From b12c4c22fb5acaa1a5d3c55700065eff1c72a0e5 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Tue, 26 Jan 2021 16:26:47 +0000 Subject: [PATCH 1/2] Add command for creating TLS secrets The image-reflector controller now accepts a secret containing a client certificate and key, and/or a CA certificate; so it's useful to have a command for creating them. `flux create secret helm` is close, but accepts username/password (which would be ignored), and has the wrong name of course. Happily though, much can be shared between the implementations. Signed-off-by: Michael Bridgen --- cmd/flux/create_secret_helm.go | 32 +------ cmd/flux/create_secret_tls.go | 138 +++++++++++++++++++++++++++++ docs/cmd/flux_create_secret.md | 1 + docs/cmd/flux_create_secret_tls.md | 56 ++++++++++++ go.mod | 1 + 5 files changed, 200 insertions(+), 28 deletions(-) create mode 100644 cmd/flux/create_secret_tls.go create mode 100644 docs/cmd/flux_create_secret_tls.md diff --git a/cmd/flux/create_secret_helm.go b/cmd/flux/create_secret_helm.go index e137c12a..355afd7a 100644 --- a/cmd/flux/create_secret_helm.go +++ b/cmd/flux/create_secret_helm.go @@ -19,7 +19,6 @@ package main import ( "context" "fmt" - "io/ioutil" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" @@ -58,9 +57,7 @@ The create secret helm command generates a Kubernetes secret with basic authenti type secretHelmFlags struct { username string password string - certFile string - keyFile string - caFile string + secretTLSFlags } var secretHelmArgs secretHelmFlags @@ -68,10 +65,7 @@ var secretHelmArgs secretHelmFlags func init() { createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.username, "username", "u", "", "basic authentication username") createSecretHelmCmd.Flags().StringVarP(&secretHelmArgs.password, "password", "p", "", "basic authentication password") - createSecretHelmCmd.Flags().StringVar(&secretHelmArgs.certFile, "cert-file", "", "TLS authentication cert file path") - createSecretHelmCmd.Flags().StringVar(&secretHelmArgs.keyFile, "key-file", "", "TLS authentication key file path") - createSecretHelmCmd.Flags().StringVar(&secretHelmArgs.caFile, "ca-file", "", "TLS authentication CA file path") - + initSecretTLSFlags(createSecretHelmCmd.Flags(), &secretHelmArgs.secretTLSFlags) createSecretCmd.AddCommand(createSecretHelmCmd) } @@ -100,26 +94,8 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error { secret.StringData["password"] = secretHelmArgs.password } - if secretHelmArgs.certFile != "" && secretHelmArgs.keyFile != "" { - cert, err := ioutil.ReadFile(secretHelmArgs.certFile) - if err != nil { - return fmt.Errorf("failed to read repository cert file '%s': %w", secretHelmArgs.certFile, err) - } - secret.StringData["certFile"] = string(cert) - - key, err := ioutil.ReadFile(secretHelmArgs.keyFile) - if err != nil { - return fmt.Errorf("failed to read repository key file '%s': %w", secretHelmArgs.keyFile, err) - } - secret.StringData["keyFile"] = string(key) - } - - if secretHelmArgs.caFile != "" { - ca, err := ioutil.ReadFile(secretHelmArgs.caFile) - if err != nil { - return fmt.Errorf("failed to read repository CA file '%s': %w", secretHelmArgs.caFile, err) - } - secret.StringData["caFile"] = string(ca) + if err = populateSecretTLS(&secret, secretHelmArgs.secretTLSFlags); err != nil { + return err } if createArgs.export { diff --git a/cmd/flux/create_secret_tls.go b/cmd/flux/create_secret_tls.go new file mode 100644 index 00000000..70627963 --- /dev/null +++ b/cmd/flux/create_secret_tls.go @@ -0,0 +1,138 @@ +/* +Copyright 2020, 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io/ioutil" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/fluxcd/flux2/internal/utils" +) + +var createSecretTLSCmd = &cobra.Command{ + Use: "tls [name]", + Short: "Create or update a Kubernetes secret with TLS certificates", + Long: ` +The create secret tls command generates a Kubernetes secret with certificates for use with TLS.`, + Example: ` + # Create a TLS secret on disk and encrypt it with Mozilla SOPS. + # Files are expected to be PEM-encoded. + flux create secret tls certs \ + --namespace=my-namespace \ + --cert-file=./client.crt \ + --key-file=./client.key \ + --export > certs.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place certs.yaml +`, + RunE: createSecretTLSCmdRun, +} + +type secretTLSFlags struct { + certFile string + keyFile string + caFile string +} + +var secretTLSArgs secretTLSFlags + +func initSecretTLSFlags(flags *pflag.FlagSet, args *secretTLSFlags) { + flags.StringVar(&args.certFile, "cert-file", "", "TLS authentication cert file path") + flags.StringVar(&args.keyFile, "key-file", "", "TLS authentication key file path") + flags.StringVar(&args.caFile, "ca-file", "", "TLS authentication CA file path") +} + +func init() { + flags := createSecretTLSCmd.Flags() + initSecretTLSFlags(flags, &secretTLSArgs) + createSecretCmd.AddCommand(createSecretTLSCmd) +} + +func populateSecretTLS(secret *corev1.Secret, args secretTLSFlags) error { + if args.certFile != "" && args.keyFile != "" { + cert, err := ioutil.ReadFile(args.certFile) + if err != nil { + return fmt.Errorf("failed to read repository cert file '%s': %w", args.certFile, err) + } + secret.StringData["certFile"] = string(cert) + + key, err := ioutil.ReadFile(args.keyFile) + if err != nil { + return fmt.Errorf("failed to read repository key file '%s': %w", args.keyFile, err) + } + secret.StringData["keyFile"] = string(key) + } + + if args.caFile != "" { + ca, err := ioutil.ReadFile(args.caFile) + if err != nil { + return fmt.Errorf("failed to read repository CA file '%s': %w", args.caFile, err) + } + secret.StringData["caFile"] = string(ca) + } + return nil +} + +func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("secret name is required") + } + name := args[0] + + secretLabels, err := parseLabels() + if err != nil { + return err + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: rootArgs.namespace, + Labels: secretLabels, + }, + StringData: map[string]string{}, + } + if err = populateSecretTLS(&secret, secretTLSArgs); err != nil { + return err + } + + if createArgs.export { + return exportSecret(secret) + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext) + if err != nil { + return err + } + + if err := upsertSecret(ctx, kubeClient, secret); err != nil { + return err + } + logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace) + + return nil +} diff --git a/docs/cmd/flux_create_secret.md b/docs/cmd/flux_create_secret.md index 791bd8b4..7c7bf3ab 100644 --- a/docs/cmd/flux_create_secret.md +++ b/docs/cmd/flux_create_secret.md @@ -30,4 +30,5 @@ The create source sub-commands generate Kubernetes secrets specific to Flux. * [flux create](flux_create.md) - Create or update sources and resources * [flux create secret git](flux_create_secret_git.md) - Create or update a Kubernetes secret for Git authentication * [flux create secret helm](flux_create_secret_helm.md) - Create or update a Kubernetes secret for Helm repository authentication +* [flux create secret tls](flux_create_secret_tls.md) - Create or update a Kubernetes secret with TLS certificates diff --git a/docs/cmd/flux_create_secret_tls.md b/docs/cmd/flux_create_secret_tls.md new file mode 100644 index 00000000..020d3e1b --- /dev/null +++ b/docs/cmd/flux_create_secret_tls.md @@ -0,0 +1,56 @@ +## flux create secret tls + +Create or update a Kubernetes secret with TLS certificates + +### Synopsis + + +The create secret tls command generates a Kubernetes secret with certificates for use with TLS. + +``` +flux create secret tls [name] [flags] +``` + +### Examples + +``` + + # Create a TLS secret on disk and encrypt it with Mozilla SOPS. + # Files are expected to be PEM-encoded. + flux create secret tls certs \ + --namespace=my-namespace \ + --cert-file=./client.crt \ + --key-file=./client.key \ + --export > certs.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place certs.yaml + +``` + +### Options + +``` + --ca-file string TLS authentication CA file path + --cert-file string TLS authentication cert file path + -h, --help help for tls + --key-file string TLS authentication key file path +``` + +### Options inherited from parent commands + +``` + --context string kubernetes context to use + --export export in YAML format to stdout + --interval duration source sync interval (default 1m0s) + --kubeconfig string path to the kubeconfig file (default "~/.kube/config") + --label strings set labels on the resource (can specify multiple labels with commas: label1=value1,label2=value2) + -n, --namespace string the namespace scope for this operation (default "flux-system") + --timeout duration timeout for this operation (default 5m0s) + --verbose print generated objects +``` + +### SEE ALSO + +* [flux create secret](flux_create_secret.md) - Create or update Kubernetes secrets + diff --git a/go.mod b/go.mod index 160de9b6..cc708a51 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/manifoldco/promptui v0.7.0 github.com/olekukonko/tablewriter v0.0.4 github.com/spf13/cobra v1.1.1 + github.com/spf13/pflag v1.0.5 k8s.io/api v0.20.2 k8s.io/apiextensions-apiserver v0.20.2 k8s.io/apimachinery v0.20.2 From 263c664acdfade650128e0bf220485fa6ee2a048 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Tue, 26 Jan 2021 17:05:55 +0000 Subject: [PATCH 2/2] Factor out more common secrets command code Making the secret without data is always the same, so factor that out. Signed-off-by: Michael Bridgen --- cmd/flux/create_secret.go | 15 +++++++++++++++ cmd/flux/create_secret_git.go | 19 ++++--------------- cmd/flux/create_secret_helm.go | 14 +------------- cmd/flux/create_secret_tls.go | 12 +----------- 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/cmd/flux/create_secret.go b/cmd/flux/create_secret.go index e091652e..6ca3b438 100644 --- a/cmd/flux/create_secret.go +++ b/cmd/flux/create_secret.go @@ -39,6 +39,21 @@ func init() { createCmd.AddCommand(createSecretCmd) } +func makeSecret(name string) (corev1.Secret, error) { + secretLabels, err := parseLabels() + if err != nil { + return corev1.Secret{}, err + } + + return corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: rootArgs.namespace, + Labels: secretLabels, + }, + }, nil +} + func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error { namespacedName := types.NamespacedName{ Namespace: secret.GetNamespace(), diff --git a/cmd/flux/create_secret_git.go b/cmd/flux/create_secret_git.go index 0178fa3e..eab2cb85 100644 --- a/cmd/flux/create_secret_git.go +++ b/cmd/flux/create_secret_git.go @@ -24,8 +24,6 @@ import ( "time" "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" @@ -106,6 +104,10 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("secret name is required") } name := args[0] + secret, err := makeSecret(name) + if err != nil { + return err + } if secretGitArgs.url == "" { return fmt.Errorf("url is required") @@ -116,22 +118,9 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("git URL parse failed: %w", err) } - secretLabels, err := parseLabels() - if err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: rootArgs.namespace, - Labels: secretLabels, - }, - } - switch u.Scheme { case "ssh": pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve) diff --git a/cmd/flux/create_secret_helm.go b/cmd/flux/create_secret_helm.go index 355afd7a..7ad282f9 100644 --- a/cmd/flux/create_secret_helm.go +++ b/cmd/flux/create_secret_helm.go @@ -21,8 +21,6 @@ import ( "fmt" "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fluxcd/flux2/internal/utils" ) @@ -74,21 +72,11 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("secret name is required") } name := args[0] - - secretLabels, err := parseLabels() + secret, err := makeSecret(name) if err != nil { return err } - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: rootArgs.namespace, - Labels: secretLabels, - }, - StringData: map[string]string{}, - } - if secretHelmArgs.username != "" && secretHelmArgs.password != "" { secret.StringData["username"] = secretHelmArgs.username secret.StringData["password"] = secretHelmArgs.password diff --git a/cmd/flux/create_secret_tls.go b/cmd/flux/create_secret_tls.go index 70627963..0a7800ec 100644 --- a/cmd/flux/create_secret_tls.go +++ b/cmd/flux/create_secret_tls.go @@ -24,7 +24,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fluxcd/flux2/internal/utils" ) @@ -99,20 +98,11 @@ func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("secret name is required") } name := args[0] - - secretLabels, err := parseLabels() + secret, err := makeSecret(name) if err != nil { return err } - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: rootArgs.namespace, - Labels: secretLabels, - }, - StringData: map[string]string{}, - } if err = populateSecretTLS(&secret, secretTLSArgs); err != nil { return err }