diff --git a/cmd/gotk/create_kustomization.go b/cmd/gotk/create_kustomization.go index 4c6d3d12..d2eb02cb 100644 --- a/cmd/gotk/create_kustomization.go +++ b/cmd/gotk/create_kustomization.go @@ -73,15 +73,17 @@ var createKsCmd = &cobra.Command{ } var ( - ksSource string - ksPath string - ksPrune bool - ksDependsOn []string - ksValidation string - ksHealthCheck []string - ksHealthTimeout time.Duration - ksSAName string - ksSANamespace string + ksSource string + ksPath string + ksPrune bool + ksDependsOn []string + ksValidation string + ksHealthCheck []string + ksHealthTimeout time.Duration + ksSAName string + ksSANamespace string + ksDecryptionProvider string + ksDecryptionSecret string ) func init() { @@ -94,6 +96,8 @@ func init() { createKsCmd.Flags().StringArrayVar(&ksDependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied") createKsCmd.Flags().StringVar(&ksSAName, "sa-name", "", "service account name") createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace") + createKsCmd.Flags().StringVar(&ksDecryptionProvider, "decryption-provider", "", "enables secrets decryption, provider can be 'sops'") + createKsCmd.Flags().StringVar(&ksDecryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption") createCmd.AddCommand(createKsCmd) } @@ -178,6 +182,21 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error { } } + if ksDecryptionProvider != "" { + if !utils.containsItemString(supportedDecryptionProviders, ksDecryptionProvider) { + return fmt.Errorf("decryption provider %s is not supported, can be %v", + ksDecryptionProvider, supportedDecryptionProviders) + } + + kustomization.Spec.Decryption = &kustomizev1.Decryption{ + Provider: ksDecryptionProvider, + } + + if ksDecryptionSecret != "" { + kustomization.Spec.Decryption.SecretRef = &corev1.LocalObjectReference{Name: ksDecryptionSecret} + } + } + if export { return exportKs(kustomization) } diff --git a/cmd/gotk/main.go b/cmd/gotk/main.go index cad7015b..03e1afbc 100644 --- a/cmd/gotk/main.go +++ b/cmd/gotk/main.go @@ -104,11 +104,12 @@ var ( ) var ( - defaultComponents = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"} - defaultVersion = "latest" - defaultNamespace = "gitops-system" - defaultNotification = "notification-controller" - supportedArch = []string{"arm64", "amd64"} + defaultComponents = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"} + defaultVersion = "latest" + defaultNamespace = "gitops-system" + defaultNotification = "notification-controller" + supportedArch = []string{"arm64", "amd64"} + supportedDecryptionProviders = []string{"sops"} ) func init() { diff --git a/docs/cmd/gotk_create_kustomization.md b/docs/cmd/gotk_create_kustomization.md index 171f6c0f..78893d71 100644 --- a/docs/cmd/gotk_create_kustomization.md +++ b/docs/cmd/gotk_create_kustomization.md @@ -48,6 +48,8 @@ gotk create kustomization [name] [flags] ### Options ``` + --decryption-provider string enables secrets decryption, provider can be 'sops' + --decryption-secret string set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption --depends-on stringArray Kustomization that must be ready before this Kustomization can be applied --health-check stringArray workload to be included in the health assessment, in the format '/.' --health-check-timeout duration timeout of health checking operations (default 2m0s) diff --git a/docs/guides/mozilla-sops.md b/docs/guides/mozilla-sops.md new file mode 100644 index 00000000..b210a933 --- /dev/null +++ b/docs/guides/mozilla-sops.md @@ -0,0 +1,166 @@ +# Manage Kubernetes secrets with Mozilla SOPS + +In order to store secrets safely in a public or private Git repository, you can use +Mozilla's [SOPS](https://github.com/mozilla/sops) CLI to encrypt +Kubernetes secrets with OpenPGP, AWS KMS, GCP KMS and Azure Key Vault. + +## Prerequisites + +To follow this guide you'll need a Kubernetes cluster with the GitOps +toolkit controllers installed on it. +Please see the [get started guide](../get-started/index.md) +or the [installation guide](installation.md). + +Install [gnupg](https://www.gnupg.org/) and [sops](https://github.com/mozilla/sops): + +```sh +brew install gnupg sops +``` + +## Generate a GPG key + +Generate a GPG key with OpenPGP without specifying a passphrase: + +```console +$ gpg --full-generate-key + +Real name: stefanprodan +Email address: stefanprodan@users.noreply.github.com +Comment: +You selected this USER-ID: + "stefanprodan " +``` + +Retrieve the GPG key ID (second row of the sec column): + +```console +$ gpg --list-secret-keys stefanprodan@users.noreply.github.com + +sec rsa3072 2020-09-06 [SC] + 1F3D1CED2F865F5E59CA564553241F147E7C5FA4 +``` + +Export the public and private keypair from your local GPG keyring and +create a Kubernetes secret named `sops-gpg` in the `gitops-system` namespace: + +```sh +gpg --export-secret-keys \ +--armor 1F3D1CED2F865F5E59CA564553241F147E7C5FA4 | +kubectl create secret generic sops-gpg \ +--namespace=gitops-system \ +--from-file=sops.asc=/dev/stdin +``` + +## Encrypt secrets + +Generate a Kubernetes secret manifest with kubectl: + +```sh +kubectl -n default create secret generic basic-auth \ +--from-literal=user=admin \ +--from-literal=password=change-me \ +--dry-run=client \ +-o yaml > basic-auth.yaml +``` + +Encrypt the secret with sops using your GPG key: + +```sh +sops --encrypt \ +--pgp=1F3D1CED2F865F5E59CA564553241F147E7C5FA4 \ +--encrypted-regex '^(data|stringData)$' \ +--in-place basic-auth.yaml +``` + +!!! hint + Note that you should encrypt only the `data` section. Encrypting the Kubernetes + secret metadata, kind or apiVersion is not supported by kustomize-controller. + +You can now commit the encrypted secret to your Git repository. + +## Configure secrets decryption + +Registry the Git repository on your cluster: + +```sh +gotk create source git my-secrets \ +--url=https://github.com/my-org/my-secrets +``` + +Create a kustomization for reconciling the secrets on the cluster: + +```sh +gotk create kustomization my-secrets \ +--source=my-secrets \ +--prune=true \ +--interval=10m \ +--decryption-provider=sops \ +--decryption-secret=sops-gpg +``` + +Note that the `sops-gpg` can contain more than one key, sops will try to decrypt the +secrets by iterating over all the private keys until it finds one that works. + +!!! hint KMS + When using AWS/GCP KMS or Azure Key Vault, you'll have to bind an IAM Role + with read access to the KMS keys to the `default` service account of the + `gitops-system` namespace for kustomize-controller to be able to fetch + keys from KMS. + +## GitOps workflow + +A cluster admin should create the Kubernetes secret with the PGP keys on each cluster and +add the GitRepository/Kustomization manifests to the fleet repository. + +Git repository manifest: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1alpha1 +kind: GitRepository +metadata: + name: my-secrets + namespace: gitops-system +spec: + interval: 1m + url: https://github.com/my-org/my-secrets +``` + +Kustomization manifest: + +```yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1alpha1 +kind: Kustomization +metadata: + name: my-secrets + namespace: gitops-system +spec: + interval: 10m0s + sourceRef: + kind: GitRepository + name: my-secrets + path: ./ + prune: true + decryption: + provider: sops + secretRef: + name: sops-gpg +``` + +!!! hint + You can generate the above manifests using `gotk create --export > manifest.yaml`. + +Assuming a team member wants to deploy an application that needs to connect +to a database using a username and password, they'll be doing the following: + +* create a Kubernetes Secret manifest locally with the db credentials e.g. `db-auth.yaml` +* encrypt the secret `data` field with sops +* create a Kubernetes Deployment manifest for the app e.g. `app-deployment.yaml` +* add the Secret to the Deployment manifest as a [volume mount or env var](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets) +* commit the manifests `db-auth.yaml` and `app-deployment.yaml` to a Git repository that's being synced by the GitOps toolkit controllers + +Once the manifests have been pushed to the Git repository, the following happens: + +* source-controller pulls the changes from Git +* kustomize-controller loads the GPG keys from the `sops-pgp` secret +* kustomize-controller decrypts the Kubernetes secrets with sops and applies them on the cluster +* kubelet creates the pods and mounts the secret as a volume or env variable inside the app container diff --git a/mkdocs.yml b/mkdocs.yml index 135d9e54..ef9c36cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,7 @@ nav: - Setup Notifications: guides/notifications.md - Setup Webhook Receivers: guides/webhook-receivers.md - Sealed Secrets: guides/sealed-secrets.md + - Mozilla SOPS: guides/mozilla-sops.md - Toolkit Components: - Source Controller: - Overview: components/source/controller.md