Add sourcesecret and kustomization manifestgen
This includes a change to the `sync` generator to make the deploy secret name configurable. Signed-off-by: Hidde Beydals <hello@hidde.co>
This commit is contained in:
@@ -19,13 +19,11 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
@@ -36,6 +34,7 @@ import (
|
|||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
|
kus "github.com/fluxcd/flux2/pkg/manifestgen/kustomization"
|
||||||
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -200,6 +199,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
|
|||||||
URL: url,
|
URL: url,
|
||||||
Branch: branch,
|
Branch: branch,
|
||||||
Interval: interval,
|
Interval: interval,
|
||||||
|
Secret: namespace,
|
||||||
TargetPath: targetPath,
|
TargetPath: targetPath,
|
||||||
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
|
ManifestFile: sync.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
@@ -214,9 +214,19 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
outputDir := filepath.Dir(output)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
|
if _, err = kustomization.WriteFile(tmpDir); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
return outputDir, nil
|
return outputDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,35 +279,6 @@ func shouldCreateDeployKey(ctx context.Context, kubeClient client.Client, namesp
|
|||||||
return false
|
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) {
|
func checkIfBootstrapPathDiffers(ctx context.Context, kubeClient client.Client, namespace string, path string) (string, bool) {
|
||||||
namespacedName := types.NamespacedName{
|
namespacedName := types.NamespacedName{
|
||||||
Name: namespace,
|
Name: namespace,
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/git"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
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/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bootstrapGitHubCmd = &cobra.Command{
|
var bootstrapGitHubCmd = &cobra.Command{
|
||||||
@@ -244,44 +244,48 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("install completed")
|
logger.Successf("install completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
repoURL := repository.GetURL()
|
repoURL := repository.GetSSH()
|
||||||
|
secretOpts := sourcesecret.Options{
|
||||||
if bootstrapArgs.tokenAuth {
|
|
||||||
// setup HTTPS token auth
|
|
||||||
secret := corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: rootArgs.namespace,
|
Name: rootArgs.namespace,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: rootArgs.namespace,
|
||||||
},
|
|
||||||
StringData: map[string]string{
|
|
||||||
"username": "git",
|
|
||||||
"password": ghToken,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
if bootstrapArgs.tokenAuth {
|
||||||
return err
|
// Setup HTTPS token auth
|
||||||
}
|
repoURL = repository.GetURL()
|
||||||
} else {
|
secretOpts.Username = "git"
|
||||||
// setup SSH deploy key
|
secretOpts.Password = ghToken
|
||||||
repoURL = repository.GetSSH()
|
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||||
if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
// Setup SSH auth
|
||||||
logger.Actionf("configuring deploy key")
|
u, err := url.Parse(repoURL)
|
||||||
u, err := url.Parse(repository.GetSSH())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("git URL parse failed: %w", err)
|
return fmt.Errorf("git URL parse failed: %w", err)
|
||||||
}
|
}
|
||||||
|
secretOpts.SSHHostname = u.Hostname()
|
||||||
key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm
|
||||||
if err != nil {
|
secretOpts.RSAKeyBits = 2048
|
||||||
return fmt.Errorf("generating deploy key failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||||
keyName := "flux"
|
keyName := "flux"
|
||||||
if githubArgs.path != "" {
|
if githubArgs.path != "" {
|
||||||
keyName = fmt.Sprintf("flux-%s", 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
|
return err
|
||||||
} else if changed {
|
} else if changed {
|
||||||
logger.Successf("deploy key configured")
|
logger.Successf("deploy key configured")
|
||||||
|
|||||||
@@ -29,12 +29,13 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
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/pkg/git"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bootstrapGitLabCmd = &cobra.Command{
|
var bootstrapGitLabCmd = &cobra.Command{
|
||||||
@@ -218,44 +219,48 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Successf("install completed")
|
logger.Successf("install completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
repoURL := repository.GetURL()
|
repoURL := repository.GetSSH()
|
||||||
|
secretOpts := sourcesecret.Options{
|
||||||
if bootstrapArgs.tokenAuth {
|
|
||||||
// setup HTTPS token auth
|
|
||||||
secret := corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: rootArgs.namespace,
|
Name: rootArgs.namespace,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: rootArgs.namespace,
|
||||||
},
|
|
||||||
StringData: map[string]string{
|
|
||||||
"username": "git",
|
|
||||||
"password": glToken,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
if bootstrapArgs.tokenAuth {
|
||||||
return err
|
// Setup HTTPS token auth
|
||||||
}
|
repoURL = repository.GetURL()
|
||||||
} else {
|
secretOpts.Username = "git"
|
||||||
// setup SSH deploy key
|
secretOpts.Password = glToken
|
||||||
repoURL = repository.GetSSH()
|
} else if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
||||||
if shouldCreateDeployKey(ctx, kubeClient, rootArgs.namespace) {
|
// Setup SSH auth
|
||||||
logger.Actionf("configuring deploy key")
|
|
||||||
u, err := url.Parse(repoURL)
|
u, err := url.Parse(repoURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("git URL parse failed: %w", err)
|
return fmt.Errorf("git URL parse failed: %w", err)
|
||||||
}
|
}
|
||||||
|
secretOpts.SSHHostname = u.Hostname()
|
||||||
key, err := generateDeployKey(ctx, kubeClient, u, rootArgs.namespace)
|
secretOpts.PrivateKeyAlgorithm = sourcesecret.RSAPrivateKeyAlgorithm
|
||||||
if err != nil {
|
secretOpts.RSAKeyBits = 2048
|
||||||
return fmt.Errorf("generating deploy key failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||||
keyName := "flux"
|
keyName := "flux"
|
||||||
if gitlabArgs.path != "" {
|
if gitlabArgs.path != "" {
|
||||||
keyName = fmt.Sprintf("flux-%s", 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
|
return err
|
||||||
} else if changed {
|
} else if changed {
|
||||||
logger.Successf("deploy key configured")
|
logger.Successf("deploy key configured")
|
||||||
|
|||||||
@@ -18,15 +18,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var createSecretCmd = &cobra.Command{
|
var createSecretCmd = &cobra.Command{
|
||||||
@@ -39,23 +36,6 @@ func init() {
|
|||||||
createCmd.AddCommand(createSecretCmd)
|
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 {
|
func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.Secret) error {
|
||||||
namespacedName := types.NamespacedName{
|
namespacedName := types.NamespacedName{
|
||||||
Namespace: secret.GetNamespace(),
|
Namespace: secret.GetNamespace(),
|
||||||
@@ -81,19 +61,3 @@ func upsertSecret(ctx context.Context, kubeClient client.Client, secret corev1.S
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
"github.com/fluxcd/pkg/ssh"
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createSecretGitCmd = &cobra.Command{
|
var createSecretGitCmd = &cobra.Command{
|
||||||
@@ -107,11 +107,6 @@ func createSecretGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("secret name is required")
|
return fmt.Errorf("secret name is required")
|
||||||
}
|
}
|
||||||
name := args[0]
|
name := args[0]
|
||||||
secret, err := makeSecret(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if secretGitArgs.url == "" {
|
if secretGitArgs.url == "" {
|
||||||
return fmt.Errorf("url is required")
|
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)
|
return fmt.Errorf("git URL parse failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
labels, err := parseLabels()
|
||||||
defer cancel()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := sourcesecret.Options{
|
||||||
|
Name: name,
|
||||||
|
Namespace: rootArgs.namespace,
|
||||||
|
Labels: labels,
|
||||||
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
|
}
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "ssh":
|
case "ssh":
|
||||||
pair, err := generateKeyPair(ctx, secretGitArgs.keyAlgorithm, secretGitArgs.rsaBits, secretGitArgs.ecdsaCurve)
|
opts.SSHHostname = u.Hostname()
|
||||||
if err != nil {
|
opts.PrivateKeyAlgorithm = sourcesecret.PrivateKeyAlgorithm(secretGitArgs.keyAlgorithm)
|
||||||
return err
|
opts.RSAKeyBits = int(secretGitArgs.rsaBits)
|
||||||
}
|
opts.ECDSACurve = secretGitArgs.ecdsaCurve.Curve
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
if secretGitArgs.username == "" || secretGitArgs.password == "" {
|
if secretGitArgs.username == "" || secretGitArgs.password == "" {
|
||||||
return fmt.Errorf("for Git over HTTP/S the username and password are required")
|
return fmt.Errorf("for Git over HTTP/S the username and password are required")
|
||||||
}
|
}
|
||||||
|
opts.Username = secretGitArgs.username
|
||||||
secret.StringData = map[string]string{
|
opts.Password = secretGitArgs.password
|
||||||
"username": secretGitArgs.username,
|
opts.CAFilePath = secretGitArgs.caFile
|
||||||
"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)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
return fmt.Errorf("git URL scheme '%s' not supported, can be: ssh, http and https", u.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
secret, err := sourcesecret.Generate(opts)
|
||||||
return exportSecret(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
if createArgs.export {
|
||||||
|
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, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
||||||
|
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createSecretHelmCmd = &cobra.Command{
|
var createSecretHelmCmd = &cobra.Command{
|
||||||
@@ -30,8 +33,8 @@ var createSecretHelmCmd = &cobra.Command{
|
|||||||
Short: "Create or update a Kubernetes secret for Helm repository authentication",
|
Short: "Create or update a Kubernetes secret for Helm repository authentication",
|
||||||
Long: `
|
Long: `
|
||||||
The create secret helm command generates a Kubernetes secret with basic authentication credentials.`,
|
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 \
|
flux create secret helm repo-auth \
|
||||||
--namespace=my-namespace \
|
--namespace=my-namespace \
|
||||||
--username=my-username \
|
--username=my-username \
|
||||||
@@ -72,36 +75,45 @@ func createSecretHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("secret name is required")
|
return fmt.Errorf("secret name is required")
|
||||||
}
|
}
|
||||||
name := args[0]
|
name := args[0]
|
||||||
secret, err := makeSecret(name)
|
|
||||||
|
labels, err := parseLabels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if secretHelmArgs.username != "" && secretHelmArgs.password != "" {
|
opts := sourcesecret.Options{
|
||||||
secret.StringData["username"] = secretHelmArgs.username
|
Name: name,
|
||||||
secret.StringData["password"] = secretHelmArgs.password
|
Namespace: rootArgs.namespace,
|
||||||
|
Labels: labels,
|
||||||
|
Username: secretHelmArgs.username,
|
||||||
|
Password: secretHelmArgs.password,
|
||||||
|
CAFilePath: secretHelmArgs.caFile,
|
||||||
|
CertFilePath: secretHelmArgs.certFile,
|
||||||
|
KeyFilePath: secretHelmArgs.keyFile,
|
||||||
}
|
}
|
||||||
|
secret, err := sourcesecret.Generate(opts)
|
||||||
if err = populateSecretTLS(&secret, secretHelmArgs.secretTLSFlags); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
return exportSecret(secret)
|
fmt.Println(secret.Content)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var s corev1.Secret
|
||||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createSecretTLSCmd = &cobra.Command{
|
var createSecretTLSCmd = &cobra.Command{
|
||||||
@@ -68,61 +69,48 @@ func init() {
|
|||||||
createSecretCmd.AddCommand(createSecretTLSCmd)
|
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 {
|
func createSecretTLSCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("secret name is required")
|
return fmt.Errorf("secret name is required")
|
||||||
}
|
}
|
||||||
name := args[0]
|
name := args[0]
|
||||||
secret, err := makeSecret(name)
|
|
||||||
|
labels, err := parseLabels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
if createArgs.export {
|
||||||
return exportSecret(secret)
|
fmt.Println(secret.Content)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var s corev1.Secret
|
||||||
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Actionf("secret '%s' created in '%s' namespace", name, rootArgs.namespace)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/fluxcd/pkg/apis/meta"
|
||||||
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -33,12 +35,11 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
"github.com/fluxcd/pkg/apis/meta"
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/flags"
|
"github.com/fluxcd/flux2/internal/flags"
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sourceGitFlags struct {
|
type sourceGitFlags struct {
|
||||||
@@ -121,7 +122,6 @@ func init() {
|
|||||||
|
|
||||||
func newSourceGitFlags() sourceGitFlags {
|
func newSourceGitFlags() sourceGitFlags {
|
||||||
return sourceGitFlags{
|
return sourceGitFlags{
|
||||||
keyAlgorithm: "rsa",
|
|
||||||
keyRSABits: 2048,
|
keyRSABits: 2048,
|
||||||
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
keyECDSACurve: flags.ECDSACurve{Curve: elliptic.P384()},
|
||||||
}
|
}
|
||||||
@@ -151,6 +151,9 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("git URL parse failed: %w", err)
|
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()
|
sourceLabels, err := parseLabels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -184,12 +187,13 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
gitRepository.Spec.Reference.Branch = sourceGitArgs.branch
|
gitRepository.Spec.Reference.Branch = sourceGitArgs.branch
|
||||||
}
|
}
|
||||||
|
|
||||||
if createArgs.export {
|
|
||||||
if sourceGitArgs.secretRef != "" {
|
if sourceGitArgs.secretRef != "" {
|
||||||
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
Name: sourceGitArgs.secretRef,
|
Name: sourceGitArgs.secretRef,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if createArgs.export {
|
||||||
return exportGit(gitRepository)
|
return exportGit(gitRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,18 +205,38 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
withAuth := false
|
logger.Generatef("generating GitRepository source")
|
||||||
// TODO(hidde): move all auth prep to separate func?
|
if sourceGitArgs.secretRef == "" {
|
||||||
if sourceGitArgs.secretRef != "" {
|
secretOpts := sourcesecret.Options{
|
||||||
withAuth = true
|
Name: name,
|
||||||
} else if u.Scheme == "ssh" {
|
Namespace: rootArgs.namespace,
|
||||||
logger.Generatef("generating deploy key pair")
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
pair, err := generateKeyPair(ctx, sourceGitArgs.keyAlgorithm, sourceGitArgs.keyRSABits, sourceGitArgs.keyECDSACurve)
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
secret, err := sourcesecret.Generate(secretOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
var s corev1.Secret
|
||||||
logger.Successf("deploy key: %s", pair.PublicKey)
|
if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
|
return 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)
|
||||||
|
}
|
||||||
|
if ppk, ok := s.StringData[sourcesecret.PublicKeySecretKey]; ok {
|
||||||
|
logger.Generatef("deploy key: %s", ppk)
|
||||||
prompt := promptui.Prompt{
|
prompt := promptui.Prompt{
|
||||||
Label: "Have you added the deploy key to your repository",
|
Label: "Have you added the deploy key to your repository",
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
@@ -220,72 +244,15 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
if _, err := prompt.Run(); err != nil {
|
if _, err := prompt.Run(); err != nil {
|
||||||
return fmt.Errorf("aborting")
|
return fmt.Errorf("aborting")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
logger.Actionf("collecting preferred public key from SSH server")
|
logger.Actionf("applying secret with repository credentials")
|
||||||
hostKey, err := scanHostKey(ctx, u)
|
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
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 {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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{
|
gitRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
Name: secretName,
|
Name: s.Name,
|
||||||
|
}
|
||||||
|
logger.Successf("authentication configured")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||||
|
|
||||||
"github.com/fluxcd/flux2/internal/utils"
|
"github.com/fluxcd/flux2/internal/utils"
|
||||||
|
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createSourceHelmCmd = &cobra.Command{
|
var createSourceHelmCmd = &cobra.Command{
|
||||||
@@ -149,46 +151,27 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
logger.Generatef("generating HelmRepository source")
|
logger.Generatef("generating HelmRepository source")
|
||||||
if sourceHelmArgs.secretRef == "" {
|
if sourceHelmArgs.secretRef == "" {
|
||||||
secretName := fmt.Sprintf("helm-%s", name)
|
secretName := fmt.Sprintf("helm-%s", name)
|
||||||
|
secretOpts := sourcesecret.Options{
|
||||||
secret := corev1.Secret{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: secretName,
|
Name: secretName,
|
||||||
Namespace: rootArgs.namespace,
|
Namespace: rootArgs.namespace,
|
||||||
Labels: sourceLabels,
|
Username: sourceHelmArgs.username,
|
||||||
},
|
Password: sourceHelmArgs.password,
|
||||||
StringData: map[string]string{},
|
CertFilePath: sourceHelmArgs.certFile,
|
||||||
|
KeyFilePath: sourceHelmArgs.keyFile,
|
||||||
|
CAFilePath: sourceHelmArgs.caFile,
|
||||||
|
ManifestFile: sourcesecret.MakeDefaultOptions().ManifestFile,
|
||||||
}
|
}
|
||||||
|
secret, err := sourcesecret.Generate(secretOpts)
|
||||||
if sourceHelmArgs.username != "" && sourceHelmArgs.password != "" {
|
|
||||||
secret.StringData["username"] = sourceHelmArgs.username
|
|
||||||
secret.StringData["password"] = sourceHelmArgs.password
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceHelmArgs.certFile != "" && sourceHelmArgs.keyFile != "" {
|
|
||||||
cert, err := ioutil.ReadFile(sourceHelmArgs.certFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read repository cert file '%s': %w", sourceHelmArgs.certFile, err)
|
return err
|
||||||
}
|
}
|
||||||
secret.StringData["certFile"] = string(cert)
|
var s corev1.Secret
|
||||||
|
if err = yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||||
key, err := ioutil.ReadFile(sourceHelmArgs.keyFile)
|
return err
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read repository key file '%s': %w", sourceHelmArgs.keyFile, err)
|
|
||||||
}
|
}
|
||||||
secret.StringData["keyFile"] = string(key)
|
if len(s.StringData) > 0 {
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(secret.StringData) > 0 {
|
|
||||||
logger.Actionf("applying secret with repository credentials")
|
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
|
return err
|
||||||
}
|
}
|
||||||
helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
helmRepository.Spec.SecretRef = &meta.LocalObjectReference{
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ flux create secret helm [name] [flags]
|
|||||||
### Examples
|
### 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 \
|
flux create secret helm repo-auth \
|
||||||
--namespace=my-namespace \
|
--namespace=my-namespace \
|
||||||
--username=my-username \
|
--username=my-username \
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ flux create source git [name] [flags]
|
|||||||
-p, --password string basic authentication password
|
-p, --password string basic authentication password
|
||||||
--secret-ref string the name of an existing secret containing SSH or basic credentials
|
--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-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)
|
--ssh-rsa-bits rsaKeyBits SSH RSA public key bit size (multiplies of 8) (default 2048)
|
||||||
--tag string git tag
|
--tag string git tag
|
||||||
--tag-semver string git tag semver range
|
--tag-semver string git tag semver range
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -22,6 +22,7 @@ require (
|
|||||||
github.com/olekukonko/tablewriter v0.0.4
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
github.com/spf13/cobra v1.1.1
|
github.com/spf13/cobra v1.1.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0
|
||||||
k8s.io/api v0.20.2
|
k8s.io/api v0.20.2
|
||||||
k8s.io/apiextensions-apiserver v0.20.2
|
k8s.io/apiextensions-apiserver v0.20.2
|
||||||
k8s.io/apimachinery v0.20.2
|
k8s.io/apimachinery v0.20.2
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,6 +29,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"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"
|
"github.com/olekukonko/tablewriter"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
@@ -40,20 +47,6 @@ import (
|
|||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"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"
|
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||||
)
|
)
|
||||||
@@ -253,90 +246,6 @@ func MakeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference
|
|||||||
return refs
|
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) {
|
func PrintTable(writer io.Writer, header []string, rows [][]string) {
|
||||||
table := tablewriter.NewWriter(writer)
|
table := tablewriter.NewWriter(writer)
|
||||||
table.SetHeader(header)
|
table.SetHeader(header)
|
||||||
|
|||||||
123
pkg/manifestgen/kustomization/kustomization.go
Normal file
123
pkg/manifestgen/kustomization/kustomization.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
33
pkg/manifestgen/kustomization/options.go
Normal file
33
pkg/manifestgen/kustomization/options.go
Normal file
@@ -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: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
74
pkg/manifestgen/sourcesecret/options.go
Normal file
74
pkg/manifestgen/sourcesecret/options.go
Normal file
@@ -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",
|
||||||
|
}
|
||||||
|
}
|
||||||
187
pkg/manifestgen/sourcesecret/sourcesecret.go
Normal file
187
pkg/manifestgen/sourcesecret/sourcesecret.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ type Options struct {
|
|||||||
Name string
|
Name string
|
||||||
Namespace string
|
Namespace string
|
||||||
Branch string
|
Branch string
|
||||||
|
Secret string
|
||||||
TargetPath string
|
TargetPath string
|
||||||
ManifestFile string
|
ManifestFile string
|
||||||
GitImplementation string
|
GitImplementation string
|
||||||
@@ -38,6 +39,7 @@ func MakeDefaultOptions() Options {
|
|||||||
Name: "flux-system",
|
Name: "flux-system",
|
||||||
Namespace: "flux-system",
|
Namespace: "flux-system",
|
||||||
Branch: "main",
|
Branch: "main",
|
||||||
|
Secret: "flux-system",
|
||||||
ManifestFile: "gotk-sync.yaml",
|
ManifestFile: "gotk-sync.yaml",
|
||||||
TargetPath: "",
|
TargetPath: "",
|
||||||
GitImplementation: "",
|
GitImplementation: "",
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
|
|||||||
Branch: options.Branch,
|
Branch: options.Branch,
|
||||||
},
|
},
|
||||||
SecretRef: &meta.LocalObjectReference{
|
SecretRef: &meta.LocalObjectReference{
|
||||||
Name: options.Name,
|
Name: options.Secret,
|
||||||
},
|
},
|
||||||
GitImplementation: options.GitImplementation,
|
GitImplementation: options.GitImplementation,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user