Use ServiceAccountName for impersonation

Drop the ServiceAccount field in favour of ServiceAccountName to prevent privilege escalation in multi-tenancy environments.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2020-11-20 10:58:24 +02:00
parent 41a8a7eaf9
commit 0c9170241f
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
8 changed files with 30 additions and 113 deletions

View File

@ -66,9 +66,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 +100,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.

View File

@ -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

View File

@ -140,21 +140,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.

View File

@ -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)

View File

@ -163,16 +163,15 @@ bool
</tr>
<tr>
<td>
<code>serviceAccount</code><br>
<code>serviceAccountName</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.ServiceAccount">
ServiceAccount
</a>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>The Kubernetes service account used for applying the kustomization.</p>
<p>The name of the Kubernetes service account to impersonate
when reconciling this Kustomization.</p>
</td>
</tr>
<tr>
@ -596,16 +595,15 @@ bool
</tr>
<tr>
<td>
<code>serviceAccount</code><br>
<code>serviceAccountName</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.ServiceAccount">
ServiceAccount
</a>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>The Kubernetes service account used for applying the kustomization.</p>
<p>The name of the Kubernetes service account to impersonate
when reconciling this Kustomization.</p>
</td>
</tr>
<tr>
@ -779,49 +777,6 @@ Snapshot
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1beta1.ServiceAccount">ServiceAccount
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>ServiceAccount defines a reference to a Kubernetes service account.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>name</code><br>
<em>
string
</em>
</td>
<td>
<p>Name is the name of the service account being referenced.</p>
</td>
</tr>
<tr>
<td>
<code>namespace</code><br>
<em>
string
</em>
</td>
<td>
<p>Namespace is the namespace of the service account being referenced.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1beta1.Snapshot">Snapshot
</h3>
<p>

View File

@ -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: |

View File

@ -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)

View File

@ -42,9 +42,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 +218,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 +231,7 @@ spec:
url: https://github.com/stefanprodan/podinfo
ignore: |
.git/
.github/
.sops.yaml
.gitlab-ci.yml
```
@ -514,11 +516,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