diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e9b7a628..eaf15b4e 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -76,6 +76,10 @@ jobs: kubectl -n kustomize-system apply -k ./config/testdata/sops kubectl -n kustomize-system wait kustomizations/sops --for=condition=ready --timeout=4m kubectl -n test2 get secrets/test --template={{.data.password}} | base64 -d | grep test + - name: Run impersonation tests + run: | + kubectl -n impersonation apply -f ./config/testdata/impersonation + kubectl -n impersonation wait kustomizations/podinfo --for=condition=ready --timeout=4m - name: Logs run: | kubectl -n kustomize-system logs deploy/source-controller diff --git a/api/v1beta1/kustomization_types.go b/api/v1beta1/kustomization_types.go index fc6042b1..2f61d806 100644 --- a/api/v1beta1/kustomization_types.go +++ b/api/v1beta1/kustomization_types.go @@ -50,6 +50,7 @@ type KustomizationSpec struct { Interval metav1.Duration `json:"interval"` // The KubeConfig for reconciling the Kustomization on a remote cluster. + // When specified, KubeConfig takes precedence over ServiceAccountName. // +optional KubeConfig *KubeConfig `json:"kubeConfig,omitempty"` @@ -66,9 +67,10 @@ type KustomizationSpec struct { // +optional HealthChecks []CrossNamespaceObjectReference `json:"healthChecks,omitempty"` - // The Kubernetes service account used for applying the kustomization. + // The name of the Kubernetes service account to impersonate + // when reconciling this Kustomization. // +optional - ServiceAccount *ServiceAccount `json:"serviceAccount,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` // Reference of the source where the kustomization file is. // +required @@ -99,17 +101,6 @@ type KustomizationSpec struct { Validation string `json:"validation,omitempty"` } -// ServiceAccount defines a reference to a Kubernetes service account. -type ServiceAccount struct { - // Name is the name of the service account being referenced. - // +required - Name string `json:"name"` - - // Namespace is the namespace of the service account being referenced. - // +required - Namespace string `json:"namespace"` -} - // Decryption defines how decryption is handled for Kubernetes manifests. type Decryption struct { // Provider is the name of the decryption engine. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 81f9e2d8..eb218763 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -176,11 +176,6 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) { *out = make([]CrossNamespaceObjectReference, len(*in)) copy(*out, *in) } - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(ServiceAccount) - **out = **in - } out.SourceRef = in.SourceRef if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout @@ -227,21 +222,6 @@ func (in *KustomizationStatus) DeepCopy() *KustomizationStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount. -func (in *ServiceAccount) DeepCopy() *ServiceAccount { - if in == nil { - return nil - } - out := new(ServiceAccount) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Snapshot) DeepCopyInto(out *Snapshot) { *out = *in diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml index d473e9a8..030fdee4 100644 --- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml +++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml @@ -115,7 +115,8 @@ spec: type: string kubeConfig: description: The KubeConfig for reconciling the Kustomization on a - remote cluster. + remote cluster. When specified, KubeConfig takes precedence over + ServiceAccountName. properties: secretRef: description: SecretRef holds the name to a secret that contains @@ -140,21 +141,10 @@ spec: prune: description: Prune enables garbage collection. type: boolean - serviceAccount: - description: The Kubernetes service account used for applying the - kustomization. - properties: - name: - description: Name is the name of the service account being referenced. - type: string - namespace: - description: Namespace is the namespace of the service account - being referenced. - type: string - required: - - name - - namespace - type: object + serviceAccountName: + description: The name of the Kubernetes service account to impersonate + when reconciling this Kustomization. + type: string sourceRef: description: Reference of the source where the kustomization file is. diff --git a/config/testdata/impersonation/test.yaml b/config/testdata/impersonation/test.yaml new file mode 100644 index 00000000..82b41d11 --- /dev/null +++ b/config/testdata/impersonation/test.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: impersonation +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gotk-reconciler + namespace: impersonation +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gotk-reconciler + namespace: impersonation +rules: + - apiGroups: ['*'] + resources: ['*'] + verbs: ['*'] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gotk-reconciler + namespace: impersonation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gotk-reconciler +subjects: + - kind: ServiceAccount + name: gotk-reconciler + namespace: impersonation +--- +apiVersion: source.toolkit.fluxcd.io/v1beta1 +kind: GitRepository +metadata: + name: podinfo + namespace: impersonation +spec: + interval: 5m + url: https://github.com/stefanprodan/podinfo + ref: + tag: "5.0.3" +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 +kind: Kustomization +metadata: + name: podinfo + namespace: impersonation +spec: + targetNamespace: impersonation + serviceAccountName: gotk-reconciler + interval: 5m + path: "./kustomize" + prune: true + sourceRef: + kind: GitRepository + name: podinfo + validation: client + healthChecks: + - kind: Deployment + name: podinfo + namespace: impersonation + timeout: 2m diff --git a/controllers/kustomization_controller.go b/controllers/kustomization_controller.go index c3809a71..079c3160 100644 --- a/controllers/kustomization_controller.go +++ b/controllers/kustomization_controller.go @@ -645,8 +645,8 @@ func (r *KustomizationReconciler) getKubeConfig(kustomization kustomizev1.Kustom func (r *KustomizationReconciler) getServiceAccountToken(kustomization kustomizev1.Kustomization) (string, error) { namespacedName := types.NamespacedName{ - Namespace: kustomization.Spec.ServiceAccount.Namespace, - Name: kustomization.Spec.ServiceAccount.Name, + Namespace: kustomization.Namespace, + Name: kustomization.Spec.ServiceAccountName, } var serviceAccount corev1.ServiceAccount @@ -656,8 +656,8 @@ func (r *KustomizationReconciler) getServiceAccountToken(kustomization kustomize } secretName := types.NamespacedName{ - Namespace: kustomization.Spec.ServiceAccount.Namespace, - Name: kustomization.Spec.ServiceAccount.Name, + Namespace: kustomization.Namespace, + Name: kustomization.Spec.ServiceAccountName, } for _, secret := range serviceAccount.Secrets { @@ -700,7 +700,7 @@ func (r *KustomizationReconciler) apply(kustomization kustomizev1.Kustomization, cmd = fmt.Sprintf("%s --kubeconfig=%s", cmd, kubeConfig) } else { // impersonate SA - if kustomization.Spec.ServiceAccount != nil { + if kustomization.Spec.ServiceAccountName != "" { saToken, err := r.getServiceAccountToken(kustomization) if err != nil { return "", fmt.Errorf("service account impersonation failed: %w", err) diff --git a/docs/api/kustomize.md b/docs/api/kustomize.md index e0d03102..168fce11 100644 --- a/docs/api/kustomize.md +++ b/docs/api/kustomize.md @@ -122,7 +122,8 @@ KubeConfig (Optional) -

The KubeConfig for reconciling the Kustomization on a remote cluster.

+

The KubeConfig for reconciling the Kustomization on a remote cluster. +When specified, KubeConfig takes precedence over ServiceAccountName.

@@ -163,16 +164,15 @@ bool -serviceAccount
+serviceAccountName
- -ServiceAccount - +string (Optional) -

The Kubernetes service account used for applying the kustomization.

+

The name of the Kubernetes service account to impersonate +when reconciling this Kustomization.

@@ -555,7 +555,8 @@ KubeConfig (Optional) -

The KubeConfig for reconciling the Kustomization on a remote cluster.

+

The KubeConfig for reconciling the Kustomization on a remote cluster. +When specified, KubeConfig takes precedence over ServiceAccountName.

@@ -596,16 +597,15 @@ bool -serviceAccount
+serviceAccountName
- -ServiceAccount - +string (Optional) -

The Kubernetes service account used for applying the kustomization.

+

The name of the Kubernetes service account to impersonate +when reconciling this Kustomization.

@@ -779,49 +779,6 @@ Snapshot -

ServiceAccount -

-

-(Appears on: -KustomizationSpec) -

-

ServiceAccount defines a reference to a Kubernetes service account.

-
-
- - - - - - - - - - - - - - - - - -
FieldDescription
-name
- -string - -
-

Name is the name of the service account being referenced.

-
-namespace
- -string - -
-

Namespace is the namespace of the service account being referenced.

-
-
-

Snapshot

diff --git a/docs/spec/README.md b/docs/spec/README.md index 980bc476..664decc1 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -34,11 +34,12 @@ of the frontend app was deployed and if the deployment is healthy, no matter the The reconciliation process can be defined with a Kubernetes custom resource that describes a pipeline such as: - **check** if depends-on conditions are meet -- **fetch** manifests from Git repository X +- **fetch** manifests from source-controller (Git repository or S3 bucket) - **generate** a kustomization if needed - **build** the manifest using kustomization X - **decrypt** Kubernetes secrets using Mozilla SOPS - **validate** the resulting objects +- **impersonate** Kubernetes account - **apply** the objects - **prune** the objects removed from source - **verify** the deployment status @@ -76,6 +77,7 @@ The API design of the controller can be found at [kustomize.toolkit.fluxcd.io/v1 | Plain Kubernetes manifests sync | :heavy_check_mark: | :heavy_check_mark: | | Kustomize build sync | :heavy_check_mark: | :heavy_check_mark: | | Garbage collection | :heavy_check_mark: | :heavy_check_mark: | +| Secrets decryption | :heavy_check_mark: | :heavy_check_mark: | | Container image updates | :x: | :heavy_check_mark: | | Generate manifests with shell scripts | :x: | :heavy_check_mark: | diff --git a/docs/spec/v1beta1/README.md b/docs/spec/v1beta1/README.md index e0258f4d..78c83add 100644 --- a/docs/spec/v1beta1/README.md +++ b/docs/spec/v1beta1/README.md @@ -13,6 +13,7 @@ of Kubernetes objects generated with Kustomize. + [Health assessment](kustomization.md#health-assessment) + [Kustomization dependencies](kustomization.md#kustomization-dependencies) + [Role-based access control](kustomization.md#role-based-access-control) + + [Targeting remote clusters](kustomization.md#remote-clusters--cluster-api) + [Secrets decryption](kustomization.md#secrets-decryption) + [Status](kustomization.md#status) diff --git a/docs/spec/v1beta1/kustomization.md b/docs/spec/v1beta1/kustomization.md index 48e1ef1f..79e937bf 100644 --- a/docs/spec/v1beta1/kustomization.md +++ b/docs/spec/v1beta1/kustomization.md @@ -26,6 +26,7 @@ type KustomizationSpec struct { Interval metav1.Duration `json:"interval"` // The KubeConfig for reconciling the Kustomization on a remote cluster. + // When specified, KubeConfig takes precedence over ServiceAccountName. // +optional KubeConfig *KubeConfig `json:"kubeConfig,omitempty"` @@ -42,9 +43,10 @@ type KustomizationSpec struct { // +optional HealthChecks []CrossNamespaceObjectReference `json:"healthChecks,omitempty"` - // The Kubernetes service account used for applying the kustomization. + // The name of the Kubernetes service account to impersonate + // when reconciling this Kustomization. // +optional - ServiceAccount *ServiceAccount `json:"serviceAccount,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` // Reference of the source where the kustomization file is. // +required @@ -217,7 +219,7 @@ file is automatically generated for all the Kubernetes manifests in the `spec.path` and sub-directories. This expects all YAML files present under that path to be valid kubernetes manifests and needs non-kubernetes ones to be excluded using `.sourceignore` file or `spec.ignore` on `GitRepository` object. -Example of excluding gitlab ci workflows and sops rules creation files: +Example of excluding CI workflows and SOPS config files: ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta1 @@ -230,6 +232,7 @@ spec: url: https://github.com/stefanprodan/podinfo ignore: | .git/ + .github/ .sops.yaml .gitlab-ci.yml ``` @@ -514,11 +517,9 @@ metadata: name: backend namespace: webapp spec: + serviceAccountName: webapp-reconciler dependsOn: - name: common - serviceAccount: - name: webapp-reconciler - namespace: webapp interval: 5m path: "./webapp/backend/" prune: true