diff --git a/cmd/flux/create_secret.go b/cmd/flux/create_secret.go new file mode 100644 index 00000000..703c971a --- /dev/null +++ b/cmd/flux/create_secret.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 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 ( + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" +) + +var createSecretCmd = &cobra.Command{ + Use: "secret", + Short: "Create or update Kubernetes secrets", + Long: "The create source sub-commands generate Kubernetes secrets specific to Flux.", +} + +func init() { + createCmd.AddCommand(createSecretCmd) +} + +func exportSecret(secret corev1.Secret) error { + secret.TypeMeta = metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + } + + data, err := yaml.Marshal(secret) + if err != nil { + return err + } + + fmt.Println("---") + fmt.Println(resourceToString(data)) + return nil +} diff --git a/cmd/flux/create_secret_git.go b/cmd/flux/create_secret_git.go new file mode 100644 index 00000000..b9b2587d --- /dev/null +++ b/cmd/flux/create_secret_git.go @@ -0,0 +1,173 @@ +/* +Copyright 2020 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" + "crypto/elliptic" + "fmt" + "net/url" + + "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" +) + +var createSecretGitCmd = &cobra.Command{ + Use: "git [name]", + Short: "Create or update a Kubernetes secret for Git authentication", + Long: ` +The create secret git command generates a Kubernetes secret with Git credentials. +For Git over SSH, the host and SSH keys are automatically generated and stored in the secret. +For Git over HTTP/S, the provided basic authentication credentials are stored in the secret.`, + Example: ` # Create a Git SSH authentication secret using an ECDSA P-521 curve public key + + flux create secret git podinfo-auth \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --ssh-key-algorithm=ecdsa \ + --ssh-ecdsa-curve=p521 + + # Create a secret for a Git repository using basic authentication + flux create secret git podinfo-auth \ + --url=https://github.com/stefanprodan/podinfo \ + --username=username \ + --password=password + + # Create a Git SSH secret on disk and print the deploy key + flux create secret git podinfo-auth \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --export > podinfo-auth.yaml + + yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode + + # Create a Git SSH secret on disk and encrypt it with Mozilla SOPS + flux create secret git podinfo-auth \ + --namespace=apps \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --export > podinfo-auth.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place podinfo-auth.yaml +`, + RunE: createSecretGitCmdRun, +} + +var ( + secretGitURL string + secretGitUsername string + secretGitPassword string + secretGitKeyAlgorithm flags.PublicKeyAlgorithm = "rsa" + secretGitRSABits flags.RSAKeyBits = 2048 + secretGitECDSACurve = flags.ECDSACurve{Curve: elliptic.P384()} +) + +func init() { + createSecretGitCmd.Flags().StringVar(&secretGitURL, "url", "", "git address, e.g. ssh://git@host/org/repository") + createSecretGitCmd.Flags().StringVarP(&secretGitUsername, "username", "u", "", "basic authentication username") + createSecretGitCmd.Flags().StringVarP(&secretGitPassword, "password", "p", "", "basic authentication password") + createSecretGitCmd.Flags().Var(&secretGitKeyAlgorithm, "ssh-key-algorithm", sourceGitKeyAlgorithm.Description()) + createSecretGitCmd.Flags().Var(&secretGitRSABits, "ssh-rsa-bits", sourceGitRSABits.Description()) + createSecretGitCmd.Flags().Var(&secretGitECDSACurve, "ssh-ecdsa-curve", sourceGitECDSACurve.Description()) + + createSecretCmd.AddCommand(createSecretGitCmd) +} + +func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("secret name is required") + } + name := args[0] + + if secretGitURL == "" { + return fmt.Errorf("url is required") + } + + u, err := url.Parse(secretGitURL) + if err != nil { + return fmt.Errorf("git URL parse failed: %w", err) + } + + secretLabels, err := parseLabels() + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: secretLabels, + }, + } + + switch u.Scheme { + case "ssh": + pair, err := generateKeyPair(ctx) + if err != nil { + return err + } + + hostKey, err := scanHostKey(ctx, u) + if err != nil { + return err + } + + secret.Data = map[string][]byte{ + "identity": pair.PrivateKey, + "identity.pub": pair.PublicKey, + "known_hosts": hostKey, + } + + if !export { + logger.Generatef("deploy key: %s", string(pair.PublicKey)) + } + case "http", "https": + if sourceGitUsername == "" || sourceGitPassword == "" { + return fmt.Errorf("for Git over HTTP/S the username and password are required") + } + + // TODO: add cert data when it's implemented in source-controller + secret.Data = map[string][]byte{ + "username": []byte(secretGitUsername), + "password": []byte(secretGitPassword), + } + default: + return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) + } + + if export { + return exportSecret(secret) + } + + kubeClient, err := utils.KubeClient(kubeconfig, 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, namespace) + + return nil +} diff --git a/docs/cmd/flux_create.md b/docs/cmd/flux_create.md index 06522300..f708304b 100644 --- a/docs/cmd/flux_create.md +++ b/docs/cmd/flux_create.md @@ -33,6 +33,7 @@ The create sub-commands generate sources and resources. * [flux create helmrelease](flux_create_helmrelease.md) - Create or update a HelmRelease resource * [flux create kustomization](flux_create_kustomization.md) - Create or update a Kustomization resource * [flux create receiver](flux_create_receiver.md) - Create or update a Receiver resource +* [flux create secret](flux_create_secret.md) - Create or update Kubernetes secrets * [flux create source](flux_create_source.md) - Create or update sources * [flux create tenant](flux_create_tenant.md) - Create or update a tenant diff --git a/docs/cmd/flux_create_secret.md b/docs/cmd/flux_create_secret.md new file mode 100644 index 00000000..44c87f45 --- /dev/null +++ b/docs/cmd/flux_create_secret.md @@ -0,0 +1,32 @@ +## flux create secret + +Create or update Kubernetes secrets + +### Synopsis + +The create source sub-commands generate Kubernetes secrets specific to Flux. + +### Options + +``` + -h, --help help for secret +``` + +### 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](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 + diff --git a/docs/cmd/flux_create_secret_git.md b/docs/cmd/flux_create_secret_git.md new file mode 100644 index 00000000..f5ef66ea --- /dev/null +++ b/docs/cmd/flux_create_secret_git.md @@ -0,0 +1,78 @@ +## flux create secret git + +Create or update a Kubernetes secret for Git authentication + +### Synopsis + + +The create secret git command generates a Kubernetes secret with Git credentials. +For Git over SSH, the host and SSH keys are automatically generated and stored in the secret. +For Git over HTTP/S, the provided basic authentication credentials are stored in the secret. + +``` +flux create secret git [name] [flags] +``` + +### Examples + +``` + # Create a Git SSH authentication secret using an ECDSA P-521 curve public key + + flux create secret git podinfo-auth \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --ssh-key-algorithm=ecdsa \ + --ssh-ecdsa-curve=p521 + + # Create a secret for a Git repository using basic authentication + flux create secret git podinfo-auth \ + --url=https://github.com/stefanprodan/podinfo \ + --username=username \ + --password=password + + # Create a Git SSH secret on disk and print the deploy key + flux create secret git podinfo-auth \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --export > podinfo-auth.yaml + + yq read podinfo-auth.yaml 'data."identity.pub"' | base64 --decode + + # Create a Git SSH secret on disk and encrypt it with Mozilla SOPS + flux create secret git podinfo-auth \ + --namespace=apps \ + --url=ssh://git@github.com/stefanprodan/podinfo \ + --export > podinfo-auth.yaml + + sops --encrypt --encrypted-regex '^(data|stringData)$' \ + --in-place podinfo-auth.yaml + +``` + +### Options + +``` + -h, --help help for git + -p, --password string basic authentication password + --ssh-ecdsa-curve ecdsaCurve SSH ECDSA public key curve (p256, p384, p521) (default p384) + --ssh-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) (default rsa) + --ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048) + --url string git address, e.g. ssh://git@host/org/repository + -u, --username string basic authentication username +``` + +### 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/mkdocs.yml b/mkdocs.yml index 574f0b48..c8a89c8e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,6 +96,8 @@ nav: - Create alert: cmd/flux_create_alert.md - Create receiver: cmd/flux_create_receiver.md - Create tenant: cmd/flux_create_tenant.md + - Create secret: cmd/flux_create_secret.md + - Create secret git: cmd/flux_create_secret_git.md - Delete: cmd/flux_delete.md - Delete kustomization: cmd/flux_delete_kustomization.md - Delete helmrelease: cmd/flux_delete_helmrelease.md