From 2554060b3bf7652a8b6b2344a403be3c86876e63 Mon Sep 17 00:00:00 2001 From: John Reese Date: Sun, 15 Mar 2020 20:05:32 -0400 Subject: [PATCH] Add support for copying pull secrets --- .gitignore | 3 +- README.md | 17 +++++++- controller/sandbox.go | 87 ++++++++++++++++++++++++++++++++++++++++ deploy/cluster-role.yaml | 1 + 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ef1afd6..155bd98 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -sandbox-operator \ No newline at end of file +sandbox-operator +*.exe \ No newline at end of file diff --git a/README.md b/README.md index 500f72a..be628af 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,11 @@ spec: ## Configuration +### Clients + The Sandbox operator can leverage different clients, depending upon how authenitcation is configured for your cluster. -### Azure +#### Azure If Azure credentials are provided to the operators environment, it will perform a lookup of each user in the `owners` field and fetch that users `ObjectID` inside of Azure using the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/api/resources/azure-ad-overview?view=graph-rest-1.0). @@ -81,10 +83,21 @@ Your Azure Service Principal will need the following _Application_ permission fo Directory.Read.All (5778995a-e1bf-45b8-affa-663a9f3f4d04) -### Default +#### Default If no credentials are provided, the operator will create the `Role` and `ClusterRole` bindings using the values listed in the `owners` field. +### Docker Pull Secrets + +By default, the operator will not create any secrets in the provisioned namespace. + +**If the `PULL_SECRET_NAME` environment variable is set, the operator will copy your clusters pull secret to the provisioned namespace and patch the default service account.** + +`PULL_SECRET_NAME` should be the name of the pull secret that exists in your cluster. By default, the operator will look for your secret in the `default` namespace. + +To have the operator look in a different namespace for the pull secret, use the `PULL_SECRET_NAMESPACE` environment variable. + + ## Creating a Sandbox To create a Sandbox, apply a Sandbox CRD to the target cluster. diff --git a/controller/sandbox.go b/controller/sandbox.go index 12ed2a4..b5f4a0b 100644 --- a/controller/sandbox.go +++ b/controller/sandbox.go @@ -2,6 +2,7 @@ package controller import ( "context" + "encoding/json" "fmt" "log" "os" @@ -14,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -176,9 +178,59 @@ func (r *ReconcileSandbox) handleReconcile(ctx context.Context, request reconcil return fmt.Errorf("reconcile ClusterRoleBinding: %w", err) } + if os.Getenv("PULL_SECRET_NAME") != "" { + secretName := os.Getenv("PULL_SECRET_NAME") + secretData, err := getDockerSecretData(ctx, r.client, secretName) + if err != nil { + return fmt.Errorf("get secret data: %w", err) + } + + secret := getDockerSecret(sandbox, secretName, secretData) + _, err = ctrl.CreateOrUpdate(ctx, r.client, &secret, func() error { + return controllerutil.SetControllerReference(&sandbox, &secret, r.scheme) + }) + if err != nil { + return fmt.Errorf("reconcile docker Secret: %w", err) + } + + var defaultServiceAccount corev1.ServiceAccount + if err := r.client.Get(ctx, types.NamespacedName{Name: "default", Namespace: namespace.Name}, &defaultServiceAccount); err != nil { + return fmt.Errorf("get default service account: %w", err) + } + + patchBytes, err := getPatchBytes(secretName) + if err != nil { + return fmt.Errorf("get patch bytes: %w", err) + } + + patch := client.ConstantPatch(types.StrategicMergePatchType, patchBytes) + if err := r.client.Patch(ctx, &defaultServiceAccount, patch, &client.PatchOptions{}); err != nil { + return fmt.Errorf("patch service account: %w", err) + } + } + return nil } +func getPatchBytes(secretName string) ([]byte, error) { + type imagePullSecretsPatch struct { + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + } + + patch := imagePullSecretsPatch{ + ImagePullSecrets: []corev1.LocalObjectReference{ + {Name: secretName}, + }, + } + + patchString, err := json.Marshal(patch) + if err != nil { + return nil, fmt.Errorf("patching default service account: %w", err) + } + + return patchString, nil +} + func getNamespace(sandbox operatorsv1alpha1.Sandbox) corev1.Namespace { namespace := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -367,6 +419,41 @@ func getSmallResourceQuotaSpec() corev1.ResourceQuotaSpec { return resourceQuotaSpec } +func getDockerSecretData(ctx context.Context, client client.Client, secretName string) ([]byte, error) { + var secretNamespace string + if os.Getenv("PULL_SECRET_NAMESPACE") != "" { + secretNamespace = os.Getenv("PULL_SECRET_NAMESPACE") + } else { + secretNamespace = "default" + } + + var dockerSecret corev1.Secret + if err := client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: secretNamespace}, &dockerSecret); err != nil { + return nil, fmt.Errorf("get docker secret: %w", err) + } + + if _, ok := dockerSecret.Data[corev1.DockerConfigJsonKey]; !ok { + return nil, fmt.Errorf("secret missing dockerconfig data") + } + + return dockerSecret.Data[corev1.DockerConfigJsonKey], nil +} + +func getDockerSecret(sandbox operatorsv1alpha1.Sandbox, secretName string, secretData []byte) corev1.Secret { + dockerSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: "sandbox-" + sandbox.Name, + }, + Data: map[string][]byte{ + corev1.DockerConfigJsonKey: []byte(secretData), + }, + Type: corev1.SecretTypeDockerConfigJson, + } + + return dockerSecret +} + func getCommonLabels() map[string]string { commonLabels := make(map[string]string) commonLabels["app.kubernetes.io/name"] = "sandbox-operator" diff --git a/deploy/cluster-role.yaml b/deploy/cluster-role.yaml index 51bbe5f..aca4835 100644 --- a/deploy/cluster-role.yaml +++ b/deploy/cluster-role.yaml @@ -19,6 +19,7 @@ rules: - secrets - namespaces - resourcequotas + - serviceaccounts verbs: - '*' - apiGroups: