diff --git a/cmd/flux/bootstrap.go b/cmd/flux/bootstrap.go index 3243e629..ea12e0a4 100644 --- a/cmd/flux/bootstrap.go +++ b/cmd/flux/bootstrap.go @@ -19,13 +19,11 @@ package main import ( "context" "fmt" - "net/url" "path/filepath" "time" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" @@ -36,6 +34,7 @@ import ( "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" "github.com/fluxcd/flux2/pkg/manifestgen/install" + kus "github.com/fluxcd/flux2/pkg/manifestgen/kustomization" "github.com/fluxcd/flux2/pkg/manifestgen/sync" ) @@ -200,6 +199,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri URL: url, Branch: branch, Interval: interval, + Secret: namespace, TargetPath: targetPath, ManifestFile: sync.MakeDefaultOptions().ManifestFile, } @@ -214,9 +214,19 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri return "", err } outputDir := filepath.Dir(output) - if err := utils.GenerateKustomizationYaml(outputDir); err != nil { + + kusOpts := kus.MakeDefaultOptions() + kusOpts.BaseDir = tmpDir + kusOpts.TargetPath = filepath.Dir(manifest.Path) + + kustomization, err := kus.Generate(kusOpts) + if err != nil { + return "", err + } + if _, err = kustomization.WriteFile(tmpDir); err != nil { return "", err } + return outputDir, nil } @@ -269,35 +279,6 @@ func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namesp return false } -func generateDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) { - pair, err := generateKeyPair(ctx, sourceGitArgs.keyAlgorithm, sourceGitArgs.keyRSABits, sourceGitArgs.keyECDSACurve) - if err != nil { - return "", err - } - - hostKey, err := scanHostKey(ctx, url) - if err != nil { - return "", err - } - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Namespace: namespace, - }, - StringData: map[string]string{ - "identity": string(pair.PrivateKey), - "identity.pub": string(pair.PublicKey), - "known_hosts": string(hostKey), - }, - } - if err := upsertSecret(ctx, kubeClient, secret); err != nil { - return "", err - } - - return string(pair.PublicKey), nil -} - func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) { namespacedName := types.NamespacedName{ Name: namespace, diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 86275340..8fb48ac3 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -26,14 +26,14 @@ import ( "path/filepath" "time" + "github.com/fluxcd/pkg/git" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/fluxcd/pkg/git" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var bootstrapGitHubCmd = &cobra.Command{ @@ -244,44 +244,48 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { logger.Successf("install completed") } - repoURL := repository.GetURL() - + repoURL := repository.GetSSH() + secretOpts := sourcesecret.Options{ + Name: rootArgs.namespace, + Namespace: rootArgs.namespace, + } if bootstrapArgs.tokenAuth { - // setup HTTPS token auth - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: rootArgs.namespace, - Namespace: rootArgs.namespace, - }, - StringData: map[string]string{ - "username": "git", - "password": ghToken, - }, + // Setup HTTPS token auth + repoURL = repository.GetURL() + secretOpts.Username = "git" + secretOpts.Password = ghToken + } else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) { + // Setup SSH auth + u, err := url.Parse(repoURL) + if err != nil { + return fmt.Errorf("git URL parse failed: %w", err) } - if err := upsertSecret(ctx, kubeClient, secret); err != nil { + secretOpts.SSHHostname = u.Hostname() + secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm + secretOpts.RSAKeyBits = 2048 + } + + secret, err := sourcesecret.Generate(secretOpts) + if err != nil { + return err + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if len(s.StringData) > 0 { + logger.Actionf("configuring deploy key") + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } - } else { - // setup SSH deploy key - repoURL = repository.GetSSH() - if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) { - logger.Actionf("configuring deploy key") - u, err := url.Parse(repository.GetSSH()) - if err != nil { - return fmt.Errorf("git URL parse failed: %w", err) - } - - key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace) - if err != nil { - return fmt.Errorf("generating deploy key failed: %w", err) - } + if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { keyName := "flux" if githubArgs.path != "" { keyName = fmt.Sprintf("flux-%s", githubArgs.path) } - if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { + if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil { return err } else if changed { logger.Successf("deploy key configured") diff --git a/cmd/flux/bootstrap_gitlab.go b/cmd/flux/bootstrap_gitlab.go index ff41cd71..15088d31 100644 --- a/cmd/flux/bootstrap_gitlab.go +++ b/cmd/flux/bootstrap_gitlab.go @@ -29,12 +29,13 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" "github.com/fluxcd/pkg/git" "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var bootstrapGitLabCmd = &cobra.Command{ @@ -218,44 +219,48 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error { logger.Successf("install completed") } - repoURL := repository.GetURL() - + repoURL := repository.GetSSH() + secretOpts := sourcesecret.Options{ + Name: rootArgs.namespace, + Namespace: rootArgs.namespace, + } if bootstrapArgs.tokenAuth { - // setup HTTPS token auth - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: rootArgs.namespace, - Namespace: rootArgs.namespace, - }, - StringData: map[string]string{ - "username": "git", - "password": glToken, - }, + // Setup HTTPS token auth + repoURL = repository.GetURL() + secretOpts.Username = "git" + secretOpts.Password = glToken + } else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) { + // Setup SSH auth + u, err := url.Parse(repoURL) + if err != nil { + return fmt.Errorf("git URL parse failed: %w", err) } - if err := upsertSecret(ctx, kubeClient, secret); err != nil { + secretOpts.SSHHostname = u.Hostname() + secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm + secretOpts.RSAKeyBits = 2048 + } + + secret, err := sourcesecret.Generate(secretOpts) + if err != nil { + return err + } + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if len(s.StringData) > 0 { + logger.Actionf("configuring deploy key") + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } - } else { - // setup SSH deploy key - repoURL = repository.GetSSH() - if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) { - logger.Actionf("configuring deploy key") - u, err := url.Parse(repoURL) - if err != nil { - return fmt.Errorf("git URL parse failed: %w", err) - } - - key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace) - if err != nil { - return fmt.Errorf("generating deploy key failed: %w", err) - } + if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { keyName := "flux" if gitlabArgs.path != "" { keyName = fmt.Sprintf("flux-%s", gitlabArgs.path) } - if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { + if changed, err := provider.AddDeployKey(ctx, repository, ppk, keyName); err != nil { return err } else if changed { logger.Successf("deploy key configured") diff --git a/cmd/flux/create_secret.go b/cmd/flux/create_secret.go index 17139794..9aad039c 100644 --- a/cmd/flux/create_secret.go +++ b/cmd/flux/create_secret.go @@ -18,15 +18,12 @@ package main import ( "context" - "fmt" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/yaml" ) var createSecretCmd = &cobra.Command{ @@ -39,23 +36,6 @@ 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, - }, - StringData: map[string]string{}, - Data: nil, - }, nil -} - func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error { namespacedName := types.NamespacedName{ Namespace: secret.GetNamespace(), @@ -81,19 +61,3 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S } return nil } - -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 index 494f5c14..4d2dc897 100644 --- a/cmd/flux/create_secret_git.go +++ b/cmd/flux/create_secret_git.go @@ -20,15 +20,15 @@ import ( "context" "crypto/elliptic" "fmt" - "io/ioutil" "net/url" - "time" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" - "github.com/fluxcd/pkg/ssh" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var createSecretGitCmd = &cobra.Command{ @@ -107,11 +107,6 @@ 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") } @@ -121,96 +116,63 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("git URL parse failed: %w", err) } - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() + labels, err := parseLabels() + if err != nil { + return err + } + opts := sourcesecret.Options{ + Name: name, + Namespace: rootArgs.namespace, + Labels: labels, + ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, + } switch u.Scheme { case "ssh": - pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve) - if err != nil { - return err - } - - hostKey, err := scanHostKey(ctx, u) - if err != nil { - return err - } - - secret.StringData = map[string]string{ - "identity": string(pair.PrivateKey), - "identity.pub": string(pair.PublicKey), - "known_hosts": string(hostKey), - } - - if !createArgs.export { - logger.Generatef("deploy key: %s", string(pair.PublicKey)) - } + opts.SSHHostname = u.Hostname() + opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm) + opts.RSAKeyBits = int(secretGitArgs.rsaBits) + opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve case "http", "https": if secretGitArgs.username == "" || secretGitArgs.password == "" { return fmt.Errorf("for Git over HTTP/S the username and password are required") } - - secret.StringData = map[string]string{ - "username": secretGitArgs.username, - "password": secretGitArgs.password, - } - - if secretGitArgs.caFile != "" { - ca, err := ioutil.ReadFile(secretGitArgs.caFile) - if err != nil { - return fmt.Errorf("failed to read CA file '%s': %w", secretGitArgs.caFile, err) - } - secret.StringData["caFile"] = string(ca) - } - + opts.Username = secretGitArgs.username + opts.Password = secretGitArgs.password + opts.CAFilePath = secretGitArgs.caFile default: return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) } + secret, err := sourcesecret.Generate(opts) + if err != nil { + return err + } + if createArgs.export { - return exportSecret(secret) + fmt.Println(secret.Content) + return nil + } + + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err } + if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { + logger.Generatef("deploy key: %s", ppk) + } + + 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 { + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace) return nil } - -func generateKeyPair(ctx context.Context, alg flags.PublicKeyAlgorithm, rsa flags.RSAKeyBits, ecdsa flags.ECDSACurve) (*ssh.KeyPair, error) { - var keyGen ssh.KeyPairGenerator - switch algorithm := alg.String(); algorithm { - case "rsa": - keyGen = ssh.NewRSAGenerator(int(rsa)) - case "ecdsa": - keyGen = ssh.NewECDSAGenerator(ecdsa.Curve) - case "ed25519": - keyGen = ssh.NewEd25519Generator() - default: - return nil, fmt.Errorf("unsupported public key algorithm: %s", algorithm) - } - pair, err := keyGen.Generate() - if err != nil { - return nil, fmt.Errorf("key pair generation failed, error: %w", err) - } - return pair, nil -} - -func scanHostKey(ctx context.Context, url *url.URL) ([]byte, error) { - host := url.Host - if url.Port() == "" { - host = host + ":22" - } - hostKey, err := ssh.ScanHostKey(host, 30*time.Second) - if err != nil { - return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err) - } - return hostKey, nil -} diff --git a/cmd/flux/create_secret_helm.go b/cmd/flux/create_secret_helm.go index 7ad282f9..a7c14803 100644 --- a/cmd/flux/create_secret_helm.go +++ b/cmd/flux/create_secret_helm.go @@ -21,8 +21,11 @@ import ( "fmt" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var createSecretHelmCmd = &cobra.Command{ @@ -30,8 +33,8 @@ var createSecretHelmCmd = &cobra.Command{ Short: "Create or update a Kubernetes secret for Helm repository authentication", Long: ` The create secret helm command generates a Kubernetes secret with basic authentication credentials.`, - Example: ` # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS - + Example: ` + # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS flux create secret helm repo-auth \ --namespace=my-namespace \ --username=my-username \ @@ -72,36 +75,45 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error { return fmt.Errorf("secret name is required") } name := args[0] - secret, err := makeSecret(name) + + labels, err := parseLabels() if err != nil { return err } - if secretHelmArgs.username != "" && secretHelmArgs.password != "" { - secret.StringData["username"] = secretHelmArgs.username - secret.StringData["password"] = secretHelmArgs.password + opts := sourcesecret.Options{ + Name: name, + Namespace: rootArgs.namespace, + Labels: labels, + Username: secretHelmArgs.username, + Password: secretHelmArgs.password, + CAFilePath: secretHelmArgs.caFile, + CertFilePath: secretHelmArgs.certFile, + KeyFilePath: secretHelmArgs.keyFile, } - - if err = populateSecretTLS(&secret, secretHelmArgs.secretTLSFlags); err != nil { + secret, err := sourcesecret.Generate(opts) + if err != nil { return err } if createArgs.export { - return exportSecret(secret) + fmt.Println(secret.Content) + return nil } 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 { + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } - logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace) return nil } diff --git a/cmd/flux/create_secret_tls.go b/cmd/flux/create_secret_tls.go index 0a7800ec..69882fa2 100644 --- a/cmd/flux/create_secret_tls.go +++ b/cmd/flux/create_secret_tls.go @@ -19,13 +19,14 @@ package main import ( "context" "fmt" - "io/ioutil" "github.com/spf13/cobra" "github.com/spf13/pflag" corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var createSecretTLSCmd = &cobra.Command{ @@ -68,61 +69,48 @@ func init() { 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] - secret, err := makeSecret(name) + + labels, err := parseLabels() if err != nil { return err } - if err = populateSecretTLS(&secret, secretTLSArgs); err != nil { + opts := sourcesecret.Options{ + Name: name, + Namespace: rootArgs.namespace, + Labels: labels, + CAFilePath: secretTLSArgs.caFile, + CertFilePath: secretTLSArgs.certFile, + KeyFilePath: secretTLSArgs.keyFile, + } + secret, err := sourcesecret.Generate(opts) + if err != nil { return err } if createArgs.export { - return exportSecret(secret) + fmt.Println(secret.Content) + return nil } 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 { + var s corev1.Secret + if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err + } + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } - logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace) return nil } diff --git a/cmd/flux/create_source_git.go b/cmd/flux/create_source_git.go index d14ce5c8..e5599424 100644 --- a/cmd/flux/create_source_git.go +++ b/cmd/flux/create_source_git.go @@ -24,6 +24,8 @@ import ( "net/url" "os" + "github.com/fluxcd/pkg/apis/meta" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" "github.com/manifoldco/promptui" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" @@ -33,12 +35,11 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/fluxcd/pkg/apis/meta" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" + "sigs.k8s.io/yaml" "github.com/fluxcd/flux2/internal/flags" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) type sourceGitFlags struct { @@ -121,7 +122,6 @@ func init() { func newSourceGitFlags() sourceGitFlags { return sourceGitFlags{ - keyAlgorithm: "rsa", keyRSABits: 2048, keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()}, } @@ -151,6 +151,9 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("git URL parse failed: %w", err) } + if u.Scheme != "ssh" && u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme) + } sourceLabels, err := parseLabels() if err != nil { @@ -184,12 +187,13 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { gitRepository.Spec.Reference.Branch = sourceGitArgs.branch } - if createArgs.export { - if sourceGitArgs.secretRef != "" { - gitRepository.Spec.SecretRef = &meta.LocalObjectReference{ - Name: sourceGitArgs.secretRef, - } + if sourceGitArgs.secretRef != "" { + gitRepository.Spec.SecretRef = &meta.LocalObjectReference{ + Name: sourceGitArgs.secretRef, } + } + + if createArgs.export { return exportGit(gitRepository) } @@ -201,91 +205,54 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error { return err } - withAuth := false - // TODO(hidde): move all auth prep to separate func? - if sourceGitArgs.secretRef != "" { - withAuth = true - } else if u.Scheme == "ssh" { - logger.Generatef("generating deploy key pair") - pair, err := generateKeyPair(ctx, sourceGitArgs.keyAlgorithm, sourceGitArgs.keyRSABits, sourceGitArgs.keyECDSACurve) - if err != nil { - return err - } - - logger.Successf("deploy key: %s", pair.PublicKey) - prompt := promptui.Prompt{ - Label: "Have you added the deploy key to your repository", - IsConfirm: true, + logger.Generatef("generating GitRepository source") + if sourceGitArgs.secretRef == "" { + secretOpts := sourcesecret.Options{ + Name: name, + Namespace: rootArgs.namespace, + ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, } - if _, err := prompt.Run(); err != nil { - return fmt.Errorf("aborting") + switch u.Scheme { + case "ssh": + secretOpts.SSHHostname = u.Hostname() + secretOpts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(sourceGitArgs.keyAlgorithm) + secretOpts.RSAKeyBits = int(sourceGitArgs.keyRSABits) + secretOpts.ECDSACurve = sourceGitArgs.keyECDSACurve.Curve + case "https": + secretOpts.Username = sourceGitArgs.username + secretOpts.Password = sourceGitArgs.password + secretOpts.CAFilePath = sourceGitArgs.caFile } - - logger.Actionf("collecting preferred public key from SSH server") - hostKey, err := scanHostKey(ctx, u) + secret, err := sourcesecret.Generate(secretOpts) if err != nil { return err } - logger.Successf("collected public key from SSH server:\n%s", hostKey) - - logger.Actionf("applying secret with keys") - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: rootArgs.namespace, - Labels: sourceLabels, - }, - StringData: map[string]string{ - "identity": string(pair.PrivateKey), - "identity.pub": string(pair.PublicKey), - "known_hosts": string(hostKey), - }, - } - if err := upsertSecret(ctx, kubeClient, secret); err != nil { + var s corev1.Secret + if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil { return err } - withAuth = true - } else if sourceGitArgs.username != "" && sourceGitArgs.password != "" { - logger.Actionf("applying secret with basic auth credentials") - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: rootArgs.namespace, - Labels: sourceLabels, - }, - StringData: map[string]string{ - "username": sourceGitArgs.username, - "password": sourceGitArgs.password, - }, - } - - if sourceGitArgs.caFile != "" { - ca, err := ioutil.ReadFile(sourceGitArgs.caFile) - if err != nil { - return fmt.Errorf("failed to read CA file '%s': %w", sourceGitArgs.caFile, err) + if len(s.StringData) > 0 { + if hk, ok := s.StringData[sourcesecret.KnownHostsSecretKey]; ok { + logger.Successf("collected public key from SSH server:\n%s", hk) } - secret.StringData["caFile"] = string(ca) - } - - if err := upsertSecret(ctx, kubeClient, secret); err != nil { - return err - } - withAuth = true - } - - if withAuth { - logger.Successf("authentication configured") - } - - logger.Generatef("generating GitRepository source") - - if withAuth { - secretName := name - if sourceGitArgs.secretRef != "" { - secretName = sourceGitArgs.secretRef - } - gitRepository.Spec.SecretRef = &meta.LocalObjectReference{ - Name: secretName, + if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok { + logger.Generatef("deploy key: %s", ppk) + prompt := promptui.Prompt{ + Label: "Have you added the deploy key to your repository", + IsConfirm: true, + } + if _, err := prompt.Run(); err != nil { + return fmt.Errorf("aborting") + } + } + logger.Actionf("applying secret with repository credentials") + if err := upsertSecret(ctx, kubeClient, s); err != nil { + return err + } + gitRepository.Spec.SecretRef = &meta.LocalObjectReference{ + Name: s.Name, + } + logger.Successf("authentication configured") } } diff --git a/cmd/flux/create_source_helm.go b/cmd/flux/create_source_helm.go index 4c22b985..c9940b22 100644 --- a/cmd/flux/create_source_helm.go +++ b/cmd/flux/create_source_helm.go @@ -32,10 +32,12 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret" ) var createSourceHelmCmd = &cobra.Command{ @@ -149,46 +151,27 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error { logger.Generatef("generating HelmRepository source") if sourceHelmArgs.secretRef == "" { secretName := fmt.Sprintf("helm-%s", name) - - secret := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: rootArgs.namespace, - Labels: sourceLabels, - }, - StringData: map[string]string{}, - } - - if sourceHelmArgs.username != "" && sourceHelmArgs.password != "" { - secret.StringData["username"] = sourceHelmArgs.username - secret.StringData["password"] = sourceHelmArgs.password + secretOpts := sourcesecret.Options{ + Name: secretName, + Namespace: rootArgs.namespace, + Username: sourceHelmArgs.username, + Password: sourceHelmArgs.password, + CertFilePath: sourceHelmArgs.certFile, + KeyFilePath: sourceHelmArgs.keyFile, + CAFilePath: sourceHelmArgs.caFile, + ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile, } - - if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" { - cert, err := ioutil.ReadFile(sourceHelmArgs.certFile) - if err != nil { - return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmArgs.certFile, err) - } - secret.StringData["certFile"] = string(cert) - - key, err := ioutil.ReadFile(sourceHelmArgs.keyFile) - if err != nil { - return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmArgs.keyFile, err) - } - secret.StringData["keyFile"] = string(key) + secret, err := sourcesecret.Generate(secretOpts) + if err != nil { + return err } - - if sourceHelmArgs.caFile != "" { - ca, err := ioutil.ReadFile(sourceHelmArgs.caFile) - if err != nil { - return fmt.Errorf("failed to read repository CA file '%s': %w", sourceHelmArgs.caFile, err) - } - secret.StringData["caFile"] = string(ca) + var s corev1.Secret + if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil { + return err } - - if len(secret.StringData) > 0 { + if len(s.StringData) > 0 { logger.Actionf("applying secret with repository credentials") - if err := upsertSecret(ctx, kubeClient, secret); err != nil { + if err := upsertSecret(ctx, kubeClient, s); err != nil { return err } helmRepository.Spec.SecretRef = &meta.LocalObjectReference{ diff --git a/docs/cmd/flux_create_secret_helm.md b/docs/cmd/flux_create_secret_helm.md index e01bad38..641e5926 100644 --- a/docs/cmd/flux_create_secret_helm.md +++ b/docs/cmd/flux_create_secret_helm.md @@ -14,8 +14,8 @@ flux create secret helm [name] [flags] ### Examples ``` - # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS + # Create a Helm authentication secret on disk and encrypt it with Mozilla SOPS flux create secret helm repo-auth \ --namespace=my-namespace \ --username=my-username \ diff --git a/docs/cmd/flux_create_source_git.md b/docs/cmd/flux_create_source_git.md index 150a197f..99cc7f49 100644 --- a/docs/cmd/flux_create_source_git.md +++ b/docs/cmd/flux_create_source_git.md @@ -62,7 +62,7 @@ flux create source git [name] [flags] -p, --password string basic authentication password --secret-ref string the name of an existing secret containing SSH or basic credentials --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-key-algorithm publicKeyAlgorithm SSH public key algorithm (rsa, ecdsa, ed25519) --ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048) --tag string git tag --tag-semver string git tag semver range diff --git a/go.mod b/go.mod index 04da2979..be8f95ad 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/olekukonko/tablewriter v0.0.4 github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 + golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 k8s.io/api v0.20.2 k8s.io/apiextensions-apiserver v0.20.2 k8s.io/apimachinery v0.20.2 diff --git a/internal/utils/utils.go b/internal/utils/utils.go index cdfdff35..594b99b5 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -22,7 +22,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -30,6 +29,14 @@ import ( "strings" "text/template" + helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" + imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" + "github.com/fluxcd/pkg/runtime/dependency" + "github.com/fluxcd/pkg/version" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" "github.com/olekukonko/tablewriter" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -40,20 +47,6 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/kustomize/api/filesys" - "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" - "sigs.k8s.io/kustomize/api/konfig" - kustypes "sigs.k8s.io/kustomize/api/types" - "sigs.k8s.io/yaml" - - helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" - imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1" - imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" - kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1" - notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1" - "github.com/fluxcd/pkg/runtime/dependency" - "github.com/fluxcd/pkg/version" - sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" "github.com/fluxcd/flux2/pkg/manifestgen/install" ) @@ -253,90 +246,6 @@ func MakeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference return refs } -// GenerateKustomizationYaml is the equivalent of running -// 'kustomize create --autodetect' in the specified dir -func GenerateKustomizationYaml(dirPath string) error { - fs := filesys.MakeFsOnDisk() - kfile := filepath.Join(dirPath, "kustomization.yaml") - - scan := func(base string) ([]string, error) { - var paths []string - uf := kunstruct.NewKunstructuredFactoryImpl() - err := fs.Walk(base, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if path == base { - return nil - } - if info.IsDir() { - // If a sub-directory contains an existing kustomization file add the - // directory as a resource and do not decend into it. - for _, kfilename := range konfig.RecognizedKustomizationFileNames() { - if fs.Exists(filepath.Join(path, kfilename)) { - paths = append(paths, path) - return filepath.SkipDir - } - } - return nil - } - fContents, err := fs.ReadFile(path) - if err != nil { - return err - } - if _, err := uf.SliceFromBytes(fContents); err != nil { - return nil - } - paths = append(paths, path) - return nil - }) - return paths, err - } - - if _, err := os.Stat(kfile); err != nil { - abs, err := filepath.Abs(dirPath) - if err != nil { - return err - } - - files, err := scan(abs) - if err != nil { - return err - } - - f, err := fs.Create(kfile) - if err != nil { - return err - } - f.Close() - - kus := kustypes.Kustomization{ - TypeMeta: kustypes.TypeMeta{ - APIVersion: kustypes.KustomizationVersion, - Kind: kustypes.KustomizationKind, - }, - } - - var resources []string - for _, file := range files { - relP, err := filepath.Rel(abs, file) - if err != nil { - return err - } - resources = append(resources, relP) - } - - kus.Resources = resources - kd, err := yaml.Marshal(kus) - if err != nil { - return err - } - - return ioutil.WriteFile(kfile, kd, os.ModePerm) - } - return nil -} - func PrintTable(writer io.Writer, header []string, rows [][]string) { table := tablewriter.NewWriter(writer) table.SetHeader(header) diff --git a/pkg/manifestgen/kustomization/kustomization.go b/pkg/manifestgen/kustomization/kustomization.go new file mode 100644 index 00000000..4e887e9c --- /dev/null +++ b/pkg/manifestgen/kustomization/kustomization.go @@ -0,0 +1,123 @@ +/* +Copyright 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 kustomization + +import ( + "io/ioutil" + "os" + "path/filepath" + + "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/api/konfig" + kustypes "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/flux2/pkg/manifestgen" +) + +func Generate(options Options) (*manifestgen.Manifest, error) { + kfile := filepath.Join(options.TargetPath, konfig.DefaultKustomizationFileName()) + abskfile := filepath.Join(options.BaseDir, kfile) + + scan := func(base string) ([]string, error) { + var paths []string + uf := kunstruct.NewKunstructuredFactoryImpl() + err := options.FileSystem.Walk(base, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == base { + return nil + } + if info.IsDir() { + // If a sub-directory contains an existing Kustomization file add the + // directory as a resource and do not decent into it. + for _, kfilename := range konfig.RecognizedKustomizationFileNames() { + if options.FileSystem.Exists(filepath.Join(path, kfilename)) { + paths = append(paths, path) + return filepath.SkipDir + } + } + return nil + } + fContents, err := options.FileSystem.ReadFile(path) + if err != nil { + return err + } + if _, err := uf.SliceFromBytes(fContents); err != nil { + return nil + } + paths = append(paths, path) + return nil + }) + return paths, err + } + + if _, err := os.Stat(abskfile); err != nil { + abs, err := filepath.Abs(filepath.Dir(abskfile)) + if err != nil { + return nil, err + } + + files, err := scan(abs) + if err != nil { + return nil, err + } + + f, err := options.FileSystem.Create(abskfile) + if err != nil { + return nil, err + } + f.Close() + + kus := kustypes.Kustomization{ + TypeMeta: kustypes.TypeMeta{ + APIVersion: kustypes.KustomizationVersion, + Kind: kustypes.KustomizationKind, + }, + } + + var resources []string + for _, file := range files { + relP, err := filepath.Rel(abs, file) + if err != nil { + return nil, err + } + resources = append(resources, relP) + } + + kus.Resources = resources + kd, err := yaml.Marshal(kus) + if err != nil { + return nil, err + } + + return &manifestgen.Manifest{ + Path: kfile, + Content: string(kd), + }, nil + } + + kd, err := ioutil.ReadFile(abskfile) + if err != nil { + return nil, err + } + return &manifestgen.Manifest{ + Path: kfile, + Content: string(kd), + }, nil +} diff --git a/pkg/manifestgen/kustomization/options.go b/pkg/manifestgen/kustomization/options.go new file mode 100644 index 00000000..57ffb9bd --- /dev/null +++ b/pkg/manifestgen/kustomization/options.go @@ -0,0 +1,33 @@ +/* +Copyright 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 kustomization + +import "sigs.k8s.io/kustomize/api/filesys" + +type Options struct { + FileSystem filesys.FileSystem + BaseDir string + TargetPath string +} + +func MakeDefaultOptions() Options { + return Options{ + FileSystem: filesys.MakeFsOnDisk(), + BaseDir: "", + TargetPath: "", + } +} diff --git a/pkg/manifestgen/sourcesecret/options.go b/pkg/manifestgen/sourcesecret/options.go new file mode 100644 index 00000000..c5239bfb --- /dev/null +++ b/pkg/manifestgen/sourcesecret/options.go @@ -0,0 +1,74 @@ +/* +Copyright 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 sourcesecret + +import ( + "crypto/elliptic" +) + +type PrivateKeyAlgorithm string + +const ( + RSAPrivateKeyAlgorithm PrivateKeyAlgorithm = "rsa" + ECDSAPrivateKeyAlgorithm PrivateKeyAlgorithm = "ecdsa" + Ed25519PrivateKeyAlgorithm PrivateKeyAlgorithm = "ed25519" +) + +const ( + UsernameSecretKey = "username" + PasswordSecretKey = "password" + CAFileSecretKey = "caFile" + CertFileSecretKey = "certFile" + KeyFileSecretKey = "keyFile" + PrivateKeySecretKey = "identity" + PublicKeySecretKey = "identity.pub" + KnownHostsSecretKey = "known_hosts" +) + +type Options struct { + Name string + Namespace string + Labels map[string]string + SSHHostname string + PrivateKeyAlgorithm PrivateKeyAlgorithm + RSAKeyBits int + ECDSACurve elliptic.Curve + PrivateKeyPath string + Username string + Password string + CAFilePath string + CertFilePath string + KeyFilePath string + TargetPath string + ManifestFile string +} + +func MakeDefaultOptions() Options { + return Options{ + Name: "flux-system", + Namespace: "flux-system", + Labels: map[string]string{}, + PrivateKeyAlgorithm: RSAPrivateKeyAlgorithm, + PrivateKeyPath: "", + Username: "", + Password: "", + CAFilePath: "", + CertFilePath: "", + KeyFilePath: "", + ManifestFile: "secret.yaml", + } +} diff --git a/pkg/manifestgen/sourcesecret/sourcesecret.go b/pkg/manifestgen/sourcesecret/sourcesecret.go new file mode 100644 index 00000000..e5ce4f21 --- /dev/null +++ b/pkg/manifestgen/sourcesecret/sourcesecret.go @@ -0,0 +1,187 @@ +/* +Copyright 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 sourcesecret + +import ( + "bytes" + "encoding/pem" + "fmt" + "io/ioutil" + "net" + "path" + "time" + + cryptssh "golang.org/x/crypto/ssh" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/pkg/ssh" + + "github.com/fluxcd/flux2/pkg/manifestgen" +) + +const defaultSSHPort = 22 + +func Generate(options Options) (*manifestgen.Manifest, error) { + var err error + + var keypair *ssh.KeyPair + switch { + case options.Username != "", options.Password != "": + // noop + case len(options.PrivateKeyPath) > 0: + if keypair, err = loadKeyPair(options.PrivateKeyPath); err != nil { + return nil, err + } + case len(options.PrivateKeyAlgorithm) > 0: + if keypair, err = generateKeyPair(options); err != nil { + return nil, err + } + } + + var hostKey []byte + if keypair != nil { + if hostKey, err = scanHostKey(options.SSHHostname); err != nil { + return nil, err + } + } + + var caFile []byte + if options.CAFilePath != "" { + if caFile, err = ioutil.ReadFile(options.CAFilePath); err != nil { + return nil, fmt.Errorf("failed to read CA file: %w", err) + } + } + + var certFile, keyFile []byte + if options.CertFilePath != "" && options.KeyFilePath != "" { + if certFile, err = ioutil.ReadFile(options.CertFilePath); err != nil { + return nil, fmt.Errorf("failed to read cert file: %w", err) + } + if keyFile, err = ioutil.ReadFile(options.KeyFilePath); err != nil { + return nil, fmt.Errorf("failed to read key file: %w", err) + } + } + + secret := buildSecret(keypair, hostKey, caFile, certFile, keyFile, options) + b, err := yaml.Marshal(secret) + if err != nil { + return nil, err + } + + return &manifestgen.Manifest{ + Path: path.Join(options.TargetPath, options.Namespace, options.ManifestFile), + Content: fmt.Sprintf("---\n%s", resourceToString(b)), + }, nil +} + +func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile []byte, options Options) (secret corev1.Secret) { + secret.TypeMeta = metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + } + secret.ObjectMeta = metav1.ObjectMeta{ + Name: options.Name, + Namespace: options.Namespace, + } + secret.Labels = options.Labels + secret.StringData = map[string]string{} + + if options.Username != "" || options.Password != "" { + secret.StringData[UsernameSecretKey] = options.Username + secret.StringData[PasswordSecretKey] = options.Password + } + + if caFile != nil { + secret.StringData[CAFileSecretKey] = string(caFile) + } + + if certFile != nil && keyFile != nil { + secret.StringData[CertFileSecretKey] = string(certFile) + secret.StringData[KeyFileSecretKey] = string(keyFile) + } + + if keypair != nil && hostKey != nil { + secret.StringData[PrivateKeySecretKey] = string(keypair.PrivateKey) + secret.StringData[PublicKeySecretKey] = string(keypair.PublicKey) + secret.StringData[KnownHostsSecretKey] = string(hostKey) + } + + return +} + +func loadKeyPair(path string) (*ssh.KeyPair, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to open private key file: %w", err) + } + + block, _ := pem.Decode(b) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block") + } + + ppk, err := cryptssh.ParsePrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + return &ssh.KeyPair{ + PublicKey: cryptssh.MarshalAuthorizedKey(ppk.PublicKey()), + PrivateKey: b, + }, nil +} + +func generateKeyPair(options Options) (*ssh.KeyPair, error) { + var keyGen ssh.KeyPairGenerator + switch options.PrivateKeyAlgorithm { + case RSAPrivateKeyAlgorithm: + keyGen = ssh.NewRSAGenerator(options.RSAKeyBits) + case ECDSAPrivateKeyAlgorithm: + keyGen = ssh.NewECDSAGenerator(options.ECDSACurve) + case Ed25519PrivateKeyAlgorithm: + keyGen = ssh.NewEd25519Generator() + default: + return nil, fmt.Errorf("unsupported public key algorithm: %s", options.PrivateKeyAlgorithm) + } + pair, err := keyGen.Generate() + if err != nil { + return nil, fmt.Errorf("key pair generation failed, error: %w", err) + } + return pair, nil +} + +func scanHostKey(host string) ([]byte, error) { + if _, _, err := net.SplitHostPort(host); err != nil { + // Assume we are dealing with a hostname without a port, + // append the default SSH port as this is required for + // host key scanning to work. + host = fmt.Sprintf("%s:%d", host, defaultSSHPort) + } + hostKey, err := ssh.ScanHostKey(host, 30*time.Second) + if err != nil { + return nil, fmt.Errorf("SSH key scan for host %s failed, error: %w", host, err) + } + return bytes.TrimSpace(hostKey), nil +} + +func resourceToString(data []byte) string { + data = bytes.Replace(data, []byte(" creationTimestamp: null\n"), []byte(""), 1) + data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1) + return string(data) +} diff --git a/pkg/manifestgen/sync/options.go b/pkg/manifestgen/sync/options.go index 35347c23..d406435c 100644 --- a/pkg/manifestgen/sync/options.go +++ b/pkg/manifestgen/sync/options.go @@ -26,6 +26,7 @@ type Options struct { Name string Namespace string Branch string + Secret string TargetPath string ManifestFile string GitImplementation string @@ -38,6 +39,7 @@ func MakeDefaultOptions() Options { Name: "flux-system", Namespace: "flux-system", Branch: "main", + Secret: "flux-system", ManifestFile: "gotk-sync.yaml", TargetPath: "", GitImplementation: "", diff --git a/pkg/manifestgen/sync/sync.go b/pkg/manifestgen/sync/sync.go index e602b8b9..37f70f90 100644 --- a/pkg/manifestgen/sync/sync.go +++ b/pkg/manifestgen/sync/sync.go @@ -53,7 +53,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) { Branch: options.Branch, }, SecretRef: &meta.LocalObjectReference{ - Name: options.Name, + Name: options.Secret, }, GitImplementation: options.GitImplementation, },