diff --git a/charts/external-secrets/crds/pushsecret.yaml b/charts/external-secrets/crds/pushsecret.yaml new file mode 100644 index 0000000000..1a0edfb786 --- /dev/null +++ b/charts/external-secrets/crds/pushsecret.yaml @@ -0,0 +1,638 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + labels: + external-secrets.io/component: controller + name: pushsecrets.external-secrets.io +spec: + group: external-secrets.io + names: + categories: + - external-secrets + kind: PushSecret + listKind: PushSecretList + plural: pushsecrets + shortNames: + - ps + singular: pushsecret + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + - jsonPath: .status.conditions[?(@.type=="Ready")].reason + name: Status + type: string + - jsonPath: .status.refreshTime + name: Last Sync + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: PushSecret is the Schema for the PushSecrets API that enables pushing Kubernetes secrets to external secret providers. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PushSecretSpec configures the behavior of the PushSecret. + properties: + data: + description: Secret Data that should be pushed to providers + items: + description: PushSecretData defines data to be pushed to the provider and associated metadata. + properties: + conversionStrategy: + default: None + description: Used to define a conversion Strategy for the secret keys + enum: + - None + - ReverseUnicode + type: string + match: + description: Match a given Secret Key to be pushed to the provider. + properties: + remoteRef: + description: Remote Refs to push to providers. + properties: + property: + description: Name of the property in the resulting secret + type: string + remoteKey: + description: Name of the resulting provider secret. + type: string + required: + - remoteKey + type: object + secretKey: + description: Secret Key to be pushed + type: string + required: + - remoteRef + type: object + metadata: + description: |- + Metadata is metadata attached to the secret. + The structure of metadata is provider specific, please look it up in the provider documentation. + x-kubernetes-preserve-unknown-fields: true + required: + - match + type: object + type: array + dataTo: + description: DataTo defines bulk push rules that expand source Secret keys into provider entries. + items: + description: PushSecretDataTo defines how to bulk-push secrets to providers without explicit per-key mappings. + properties: + conversionStrategy: + default: None + description: Used to define a conversion Strategy for the secret keys + enum: + - None + - ReverseUnicode + type: string + match: + description: |- + Match pattern for selecting keys from the source Secret. + If not specified, all keys are selected. + properties: + regexp: + description: |- + Regexp matches keys by regular expression. + If not specified, all keys are matched. + type: string + type: object + metadata: + description: |- + Metadata is metadata attached to the secret. + The structure of metadata is provider specific, please look it up in the provider documentation. + x-kubernetes-preserve-unknown-fields: true + remoteKey: + description: |- + RemoteKey is the name of the single provider secret that will receive ALL + matched keys bundled as a JSON object (e.g. {"DB_HOST":"...","DB_USER":"..."}). + When set, per-key expansion is skipped and a single push is performed. + The provider's store prefix (if any) is still prepended to this value. + When not set, each matched key is pushed as its own individual provider secret. + type: string + rewrite: + description: |- + Rewrite operations to transform keys before pushing to the provider. + Operations are applied sequentially. + items: + description: PushSecretRewrite defines how to transform secret keys before pushing. + properties: + regexp: + description: Used to rewrite with regular expressions. + properties: + source: + description: Used to define the regular expression of a re.Compiler. + type: string + target: + description: Used to define the target pattern of a ReplaceAll operation. + type: string + required: + - source + - target + type: object + transform: + description: Used to apply string transformation on the secrets. + properties: + template: + description: |- + Used to define the template to apply on the secret name. + `.value ` will specify the secret name in the template. + type: string + required: + - template + type: object + type: object + x-kubernetes-validations: + - message: exactly one of regexp or transform must be set + rule: (has(self.regexp) && !has(self.transform)) || (!has(self.regexp) && has(self.transform)) + type: array + storeRef: + description: StoreRef specifies which SecretStore to push to. Required. + properties: + kind: + default: SecretStore + description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + enum: + - SecretStore + - ClusterSecretStore + type: string + labelSelector: + description: Optionally, sync to secret stores with label selector + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: Optionally, sync to the SecretStore of the given name + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + type: object + x-kubernetes-validations: + - message: storeRef must specify either name or labelSelector + rule: has(self.storeRef) && (has(self.storeRef.name) || has(self.storeRef.labelSelector)) + - message: 'remoteKey and rewrite are mutually exclusive: rewrite is only supported in per-key mode (without remoteKey)' + rule: '!has(self.remoteKey) || !has(self.rewrite) || size(self.rewrite) == 0' + type: array + deletionPolicy: + default: None + description: Deletion Policy to handle Secrets in the provider. + enum: + - Delete + - None + type: string + refreshInterval: + default: 1h0m0s + description: The Interval to which External Secrets will try to push a secret definition + type: string + secretStoreRefs: + items: + description: PushSecretStoreRef contains a reference on how to sync to a SecretStore. + properties: + kind: + default: SecretStore + description: Kind of the SecretStore resource (SecretStore or ClusterSecretStore) + enum: + - SecretStore + - ClusterSecretStore + type: string + labelSelector: + description: Optionally, sync to secret stores with label selector + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + name: + description: Optionally, sync to the SecretStore of the given name + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + type: array + selector: + description: The Secret Selector (k8s source) for the Push Secret + maxProperties: 1 + minProperties: 1 + properties: + generatorRef: + description: Point to a generator to create a Secret. + properties: + apiVersion: + default: generators.external-secrets.io/v1alpha1 + description: Specify the apiVersion of the generator resource + type: string + kind: + description: Specify the Kind of the generator resource + enum: + - ACRAccessToken + - ClusterGenerator + - CloudsmithAccessToken + - ECRAuthorizationToken + - Fake + - GCRAccessToken + - GithubAccessToken + - QuayAccessToken + - Password + - SSHKey + - STSSessionToken + - UUID + - VaultDynamicSecret + - Webhook + - Grafana + - MFA + type: string + name: + description: Specify the name of the generator resource + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - kind + - name + type: object + secret: + description: Select a Secret to Push. + properties: + name: + description: |- + Name of the Secret. + The Secret must exist in the same namespace as the PushSecret manifest. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + selector: + description: Selector chooses secrets using a labelSelector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + template: + description: Template defines a blueprint for the created Secret resource. + properties: + data: + additionalProperties: + type: string + type: object + engineVersion: + default: v2 + description: |- + EngineVersion specifies the template engine version + that should be used to compile/execute the + template specified in .data and .templateFrom[]. + enum: + - v2 + type: string + mergePolicy: + default: Replace + description: TemplateMergePolicy defines how the rendered template should be merged with the existing Secret data. + enum: + - Replace + - Merge + type: string + metadata: + description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint. + properties: + annotations: + additionalProperties: + type: string + type: object + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + type: object + templateFrom: + items: + description: |- + TemplateFrom specifies a source for templates. + Each item in the list can either reference a ConfigMap or a Secret resource. + properties: + configMap: + description: TemplateRef specifies a reference to either a ConfigMap or a Secret resource. + properties: + items: + description: A list of keys in the ConfigMap/Secret to use as templates for Secret data + items: + description: TemplateRefItem specifies a key in the ConfigMap/Secret to use as a template for Secret data. + properties: + key: + description: A key in the ConfigMap/Secret + maxLength: 253 + minLength: 1 + pattern: ^[-._a-zA-Z0-9]+$ + type: string + templateAs: + default: Values + description: TemplateScope specifies how the template keys should be interpreted. + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + description: The name of the ConfigMap/Secret resource + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - items + - name + type: object + literal: + type: string + secret: + description: TemplateRef specifies a reference to either a ConfigMap or a Secret resource. + properties: + items: + description: A list of keys in the ConfigMap/Secret to use as templates for Secret data + items: + description: TemplateRefItem specifies a key in the ConfigMap/Secret to use as a template for Secret data. + properties: + key: + description: A key in the ConfigMap/Secret + maxLength: 253 + minLength: 1 + pattern: ^[-._a-zA-Z0-9]+$ + type: string + templateAs: + default: Values + description: TemplateScope specifies how the template keys should be interpreted. + enum: + - Values + - KeysAndValues + type: string + required: + - key + type: object + type: array + name: + description: The name of the ConfigMap/Secret resource + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - items + - name + type: object + target: + default: Data + description: |- + Target specifies where to place the template result. + For Secret resources, common values are: "Data", "Annotations", "Labels". + For custom resources (when spec.target.manifest is set), this supports + nested paths like "spec.database.config" or "data". + type: string + type: object + type: array + type: + type: string + type: object + updatePolicy: + default: Replace + description: UpdatePolicy to handle Secrets in the provider. + enum: + - Replace + - IfNotExists + type: string + required: + - secretStoreRefs + - selector + type: object + status: + description: PushSecretStatus indicates the history of the status of PushSecret. + properties: + conditions: + items: + description: PushSecretStatusCondition indicates the status of the PushSecret. + properties: + lastTransitionTime: + format: date-time + type: string + message: + type: string + reason: + type: string + status: + type: string + type: + description: PushSecretConditionType indicates the condition of the PushSecret. + type: string + required: + - status + - type + type: object + type: array + refreshTime: + description: |- + refreshTime is the time and date the external secret was fetched and + the target secret updated + format: date-time + nullable: true + type: string + syncedPushSecrets: + additionalProperties: + additionalProperties: + description: PushSecretData defines data to be pushed to the provider and associated metadata. + properties: + conversionStrategy: + default: None + description: Used to define a conversion Strategy for the secret keys + enum: + - None + - ReverseUnicode + type: string + match: + description: Match a given Secret Key to be pushed to the provider. + properties: + remoteRef: + description: Remote Refs to push to providers. + properties: + property: + description: Name of the property in the resulting secret + type: string + remoteKey: + description: Name of the resulting provider secret. + type: string + required: + - remoteKey + type: object + secretKey: + description: Secret Key to be pushed + type: string + required: + - remoteRef + type: object + metadata: + description: |- + Metadata is metadata attached to the secret. + The structure of metadata is provider specific, please look it up in the provider documentation. + x-kubernetes-preserve-unknown-fields: true + required: + - match + type: object + type: object + description: |- + Synced PushSecrets, including secrets that already exist in provider. + Matches secret stores to PushSecretData that was stored to that secret store. + type: object + syncedResourceVersion: + description: SyncedResourceVersion keeps track of the last synced version. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/tsconfig.json b/tsconfig.json index 550404d067..77715d0b0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ "sourceMap": true, "strict": false, "strictNullChecks": true, - "target": "esnext" + "target": "esnext", + "types": ["jest", "node"] }, "exclude": ["node_modules", "dist"], "include": ["src", "jest.config.ts"], diff --git a/values/external-secrets/external-secrets-raw.gotmpl b/values/external-secrets/external-secrets-raw.gotmpl index 91817f92fc..9b768e7230 100644 --- a/values/external-secrets/external-secrets-raw.gotmpl +++ b/values/external-secrets/external-secrets-raw.gotmpl @@ -14,6 +14,14 @@ resources: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"] + - apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: eso-push-secret-writer + rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "create", "update", "patch", "delete"] - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -31,6 +39,11 @@ resources: metadata: name: core-secrets-store spec: + conditions: + - namespaceSelector: + matchExpressions: + - key: otomi.io/team + operator: DoesNotExist provider: kubernetes: remoteNamespace: apl-secrets diff --git a/values/external-secrets/external-secrets.gotmpl b/values/external-secrets/external-secrets.gotmpl index 33dee161c2..ebbfb3c650 100644 --- a/values/external-secrets/external-secrets.gotmpl +++ b/values/external-secrets/external-secrets.gotmpl @@ -21,4 +21,4 @@ installCRDs: false processClusterExternalSecret: false processClusterPushSecret: false processClusterGenerator: false -processPushSecret: false +processPushSecret: true diff --git a/values/k8s/k8s-raw-teams.gotmpl b/values/k8s/k8s-raw-teams.gotmpl index 11b4af6d50..a8cf12e47f 100644 --- a/values/k8s/k8s-raw-teams.gotmpl +++ b/values/k8s/k8s-raw-teams.gotmpl @@ -10,38 +10,12 @@ resources: labels: name: {{ $ns }} type: team + otomi.io/team: "true" {{- if $v.apps.istio.defaultRevision }} istio.io/rev: {{ $v.apps.istio.defaultRevision | quote }} {{- else }} istio-injection: enabled {{- end }} - {{- with $v.otomi | get "globalPullSecret" nil }} - {{- $gpsUsername := . | get "username" "" }} - {{- $gpsServer := . | get "server" "docker.io" }} - {{- $gpsEmail := . | get "email" "not@val.id" }} - - apiVersion: external-secrets.io/v1 - kind: ExternalSecret - metadata: - name: otomi-pullsecret-global - namespace: {{ $ns }} - spec: - refreshInterval: 1h - secretStoreRef: - name: core-secrets-store - kind: ClusterSecretStore - target: - name: otomi-pullsecret-global - creationPolicy: Owner - template: - type: kubernetes.io/dockerconfigjson - data: - .dockerconfigjson: '{"auths":{"{{ $gpsServer }}":{"username":"{{ $gpsUsername }}","password":"{{ "{{ .password | toString }}" }}","email":"{{ $gpsEmail }}"}}}' - data: - - secretKey: password - remoteRef: - key: otomi-secrets - property: globalPullSecret_password - {{- end }} # patching service account here as helm does not recognize it as it's own - apiVersion: v1 kind: ServiceAccount @@ -57,4 +31,17 @@ resources: - name: harbor-pullsecret {{- end }} {{- end }} + - apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: eso-push-secret-writer + namespace: {{ $ns }} + subjects: + - kind: ServiceAccount + name: eso-store-sa + namespace: external-secrets + roleRef: + kind: ClusterRole + name: eso-push-secret-writer + apiGroup: rbac.authorization.k8s.io {{- end }} diff --git a/values/team-secrets/team-secrets-raw.gotmpl b/values/team-secrets/team-secrets-raw.gotmpl index cc55b341e3..d984a63b3c 100644 --- a/values/team-secrets/team-secrets-raw.gotmpl +++ b/values/team-secrets/team-secrets-raw.gotmpl @@ -2,6 +2,7 @@ {{- $teamId := .Release.Labels.team }} {{- $tc := $v.teamConfig }} {{- $teamSettings := (index $tc $teamId).settings }} +{{- $teamNs := printf "team-%s" $teamId }} {{- $teamReceivers := $teamSettings | get "alerts.receivers" ($v | get "alerts.receivers" (list "slack")) }} {{- $hasReceivers := not (has "none" $teamReceivers) }} {{- $slackTpl := tpl (readFile "../../helmfile.d/snippets/alertmanager/slack.gotmpl") $v | toString }} @@ -9,100 +10,166 @@ {{- $teamAlertmanagerConfig := tpl (readFile "../../helmfile.d/snippets/alertmanager-teams.gotmpl") (dict "instance" $teamSettings "root" $v "slackTpl" $slackTpl "opsgenieTpl" $opsgenieTpl) | toString }} resources: +# ClusterSecretStore per team — ESO uses this to push secrets to the team namespace. +# Restricted to apl-secrets namespace via conditions to prevent team namespaces from using it. +- apiVersion: external-secrets.io/v1 + kind: ClusterSecretStore + metadata: + name: {{ $teamNs }}-push-store + spec: + conditions: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: apl-secrets + provider: + kubernetes: + remoteNamespace: {{ $teamNs }} + server: + url: "https://kubernetes.default.svc" + caProvider: + type: ConfigMap + name: kube-root-ca.crt + namespace: external-secrets + key: ca.crt + auth: + serviceAccount: + name: eso-store-sa + namespace: external-secrets + {{- if $teamSettings | get "managedMonitoring.grafana" false }} +# Grafana admin secret: composite in apl-secrets (literal admin-user + secret admin-password) - apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: - name: team-{{ $teamId }}-grafana-admin - namespace: team-{{ $teamId }} + name: {{ $teamNs }}-grafana-admin-composite + namespace: apl-secrets spec: refreshInterval: 1h secretStoreRef: name: core-secrets-store kind: ClusterSecretStore target: - name: team-{{ $teamId }}-grafana-admin + name: {{ $teamNs }}-grafana-admin-composite creationPolicy: Owner template: - type: Opaque data: admin-user: {{ $teamId }} - admin-password: '{{ "{{ .password | toString }}" }}' + admin-password: '{{ "{{ .settings_password | toString }}" }}' data: - - secretKey: password + - secretKey: settings_password remoteRef: key: team-{{ $teamId }}-settings-secrets property: settings_password -{{- end }} -{{- if $teamSettings | get "managedMonitoring.grafana" false }} +- apiVersion: external-secrets.io/v1alpha1 + kind: PushSecret + metadata: + name: {{ $teamNs }}-grafana-admin-push + namespace: apl-secrets + spec: + refreshInterval: 1h + deletionPolicy: Delete + secretStoreRefs: + - name: {{ $teamNs }}-push-store + kind: ClusterSecretStore + selector: + secret: + name: {{ $teamNs }}-grafana-admin-composite + data: + - match: + secretKey: admin-user + remoteRef: + remoteKey: {{ $teamNs }}-grafana-admin + property: admin-user + - match: + secretKey: admin-password + remoteRef: + remoteKey: {{ $teamNs }}-grafana-admin + property: admin-password +# Grafana OIDC secret: composite in apl-secrets (literal client_id from values + secret client_secret) - apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: - name: grafana-oidc-secret - namespace: team-{{ $teamId }} + name: {{ $teamNs }}-grafana-oidc-composite + namespace: apl-secrets spec: refreshInterval: 1h secretStoreRef: name: core-secrets-store kind: ClusterSecretStore target: - name: grafana-oidc-secret + name: {{ $teamNs }}-grafana-oidc-composite creationPolicy: Owner template: - type: Opaque data: client_id: {{ $v.apps.keycloak.idp.clientID }} - client_secret: '{{ "{{ .clientSecret | toString }}" }}' + client_secret: '{{ "{{ .idp_clientSecret | toString }}" }}' data: - - secretKey: clientSecret + - secretKey: idp_clientSecret remoteRef: key: keycloak-secrets property: idp_clientSecret -- apiVersion: external-secrets.io/v1 - kind: ExternalSecret +- apiVersion: external-secrets.io/v1alpha1 + kind: PushSecret metadata: - name: grafana-loki-datasource-secret - namespace: team-{{ $teamId }} + name: {{ $teamNs }}-grafana-oidc-push + namespace: apl-secrets spec: refreshInterval: 1h - secretStoreRef: - name: core-secrets-store - kind: ClusterSecretStore - target: - name: grafana-loki-datasource-secret - creationPolicy: Owner - template: - type: Opaque - data: - password: '{{ "{{ .adminPassword | toString }}" }}' + deletionPolicy: Delete + secretStoreRefs: + - name: {{ $teamNs }}-push-store + kind: ClusterSecretStore + selector: + secret: + name: {{ $teamNs }}-grafana-oidc-composite data: - - secretKey: adminPassword - remoteRef: - key: loki-secrets - property: adminPassword -{{- end }} -{{- if $teamSettings | get "managedMonitoring.alertmanager" false }} -- apiVersion: v1 - kind: Secret + - match: + secretKey: client_id + remoteRef: + remoteKey: grafana-oidc-secret + property: client_id + - match: + secretKey: client_secret + remoteRef: + remoteKey: grafana-oidc-secret + property: client_secret +# Grafana Loki datasource: direct push from loki-secrets (no composite needed) +- apiVersion: external-secrets.io/v1alpha1 + kind: PushSecret metadata: - name: alertmanager-team-{{ $teamId }}-config - namespace: team-{{ $teamId }} - type: Opaque - data: - alertmanager.yaml: {{ $teamAlertmanagerConfig | b64enc | nindent 6 }} -{{- if $hasReceivers }} + name: {{ $teamNs }}-grafana-loki-push + namespace: apl-secrets + spec: + refreshInterval: 1h + deletionPolicy: Delete + secretStoreRefs: + - name: {{ $teamNs }}-push-store + kind: ClusterSecretStore + selector: + secret: + name: loki-secrets + data: + - match: + secretKey: adminPassword + remoteRef: + remoteKey: grafana-loki-datasource-secret + property: password +{{- end }}{{/* managedMonitoring.grafana */}} + +{{- if and ($teamSettings | get "managedMonitoring.alertmanager" false) $hasReceivers }} +# Alertmanager credentials: composite aggregates keys from multiple source secrets - apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: - name: alertmanager-credentials - namespace: team-{{ $teamId }} + name: {{ $teamNs }}-alertmanager-composite + namespace: apl-secrets spec: refreshInterval: 1h secretStoreRef: name: core-secrets-store kind: ClusterSecretStore target: - name: alertmanager-credentials + name: {{ $teamNs }}-alertmanager-composite creationPolicy: Owner data: {{- if has "slack" $teamReceivers }} @@ -127,5 +194,105 @@ resources: key: alerts-secrets property: opsgenie_apiKey {{- end }} -{{- end }}{{/* if $hasReceivers */}} +- apiVersion: external-secrets.io/v1alpha1 + kind: PushSecret + metadata: + name: {{ $teamNs }}-alertmanager-push + namespace: apl-secrets + spec: + refreshInterval: 1h + deletionPolicy: Delete + secretStoreRefs: + - name: {{ $teamNs }}-push-store + kind: ClusterSecretStore + selector: + secret: + name: {{ $teamNs }}-alertmanager-composite + data: + {{- if has "slack" $teamReceivers }} + - match: + secretKey: slackUrl + remoteRef: + remoteKey: alertmanager-credentials + property: slackUrl + {{- end }} + {{- if has "email" $teamReceivers }} + - match: + secretKey: smtpAuthPassword + remoteRef: + remoteKey: alertmanager-credentials + property: smtpAuthPassword + - match: + secretKey: smtpAuthSecret + remoteRef: + remoteKey: alertmanager-credentials + property: smtpAuthSecret + {{- end }} + {{- if has "opsgenie" $teamReceivers }} + - match: + secretKey: opsgenieApiKey + remoteRef: + remoteKey: alertmanager-credentials + property: opsgenieApiKey + {{- end }} +{{- end }}{{/* alertmanager + hasReceivers */}} + +{{- with $v.otomi | get "globalPullSecret" nil }} +{{- $gpsUsername := . | get "username" "" }} +{{- $gpsServer := . | get "server" "docker.io" }} +{{- $gpsEmail := . | get "email" "not@val.id" }} +# Global pull secret: composite with JSON-constructed dockerconfigjson +- apiVersion: external-secrets.io/v1 + kind: ExternalSecret + metadata: + name: {{ $teamNs }}-pullsecret-composite + namespace: apl-secrets + spec: + refreshInterval: 1h + secretStoreRef: + name: core-secrets-store + kind: ClusterSecretStore + target: + name: {{ $teamNs }}-pullsecret-composite + creationPolicy: Owner + template: + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: '{"auths":{"{{ $gpsServer }}":{"username":"{{ $gpsUsername }}","password":"{{ "{{ .globalPullSecret_password | toString }}" }}","email":"{{ $gpsEmail }}"}}}' + data: + - secretKey: globalPullSecret_password + remoteRef: + key: otomi-secrets + property: globalPullSecret_password +- apiVersion: external-secrets.io/v1alpha1 + kind: PushSecret + metadata: + name: {{ $teamNs }}-pullsecret-push + namespace: apl-secrets + spec: + refreshInterval: 1h + deletionPolicy: Delete + secretStoreRefs: + - name: {{ $teamNs }}-push-store + kind: ClusterSecretStore + selector: + secret: + name: {{ $teamNs }}-pullsecret-composite + data: + - match: + secretKey: .dockerconfigjson + remoteRef: + remoteKey: otomi-pullsecret-global + property: .dockerconfigjson +{{- end }}{{/* globalPullSecret */}} + +{{- if $teamSettings | get "managedMonitoring.alertmanager" false }} +- apiVersion: v1 + kind: Secret + metadata: + name: alertmanager-team-{{ $teamId }}-config + namespace: team-{{ $teamId }} + type: Opaque + data: + alertmanager.yaml: {{ $teamAlertmanagerConfig | b64enc | nindent 6 }} {{- end }}{{/* if $teamSettings.managedMonitoring.alertmanager */}}