From 975f3bf8e5c6274b6d856503fb11791de0658678 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Wed, 9 Aug 2023 10:26:36 +0100 Subject: [PATCH 1/8] use 1 required command to avoid generating unencrypted resource --- decrypt_test.go | 2 +- main.go | 156 ++++++++++++++++++++---------------------------- secret.go | 8 +-- secret_test.go | 2 +- 4 files changed, 71 insertions(+), 97 deletions(-) diff --git a/decrypt_test.go b/decrypt_test.go index a0f8e46..7373a34 100644 --- a/decrypt_test.go +++ b/decrypt_test.go @@ -142,7 +142,7 @@ func Test_getKeyRingData(t *testing.T) { } func Test_ensureDecryption(t *testing.T) { - secretAllowedNamespacesAnnotation = "argocd.voodoobox.plugin.io/allowed-namespaces" + allowedNamespacesSecretAnnotation = "argocd.voodoobox.plugin.io/allowed-namespaces" // read keyring file kr, err := os.ReadFile(encryptedTestDir1 + "/.keyRing") diff --git a/main.go b/main.go index 1315949..75bab64 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ const ( var ( kubeClient kubernetes.Interface - secretAllowedNamespacesAnnotation string + allowedNamespacesSecretAnnotation string logger = hclog.New(&hclog.LoggerOptions{ Name: "argocd-voodoobox-plugin", @@ -46,9 +46,9 @@ type secretInfo struct { key string } -// following ENVs are set by argo-cd while running plugin commands -// https://argo-cd.readthedocs.io/en/latest/user-guide/build-environment -var commonFlags = []cli.Flag{ +var flags = []cli.Flag{ + // following 2 ENVs are set by argo-cd while running plugin commands + // https://argo-cd.readthedocs.io/en/latest/user-guide/build-environment // app-name is set by argo-cd as '_' &cli.StringFlag{ Name: "app-name", @@ -62,100 +62,65 @@ var commonFlags = []cli.Flag{ Usage: "destination application namespace ENV set by argocd", Required: true, }, + + &cli.StringFlag{ + Name: "allowed-namespaces-secret-annotation", + EnvVars: []string{"AVP_ALLOWED_NS_SECRET_ANNOTATION"}, + Usage: `when shared secret is used this value is the annotation key to look for in secret +to get comma-separated list of all the namespaces that are allowed to use it`, + Destination: &allowedNamespacesSecretAnnotation, + Value: "argocd.voodoobox.plugin.io/allowed-namespaces", + }, + + // strongbox secrets flags + &cli.StringFlag{ + Name: "app-strongbox-secret-namespace", + EnvVars: []string{argocdAppEnvPrefix + "STRONGBOX_SECRET_NAMESPACE"}, + Usage: `set 'STRONGBOX_SECRET_NAMESPACE' in argocd application as plugin ENV. the value should be the +name of a namespace where secret resource containing strongbox keyring is located`, + }, + &cli.StringFlag{ + Name: "app-strongbox-secret-key", + EnvVars: []string{argocdAppEnvPrefix + "STRONGBOX_SECRET_KEY"}, + Usage: `set 'STRONGBOX_KEYRING_KEY' in argocd application as plugin ENV, the value should be the +name of the secret data key which contains a valid strongbox keyring file`, + Value: ".strongbox_keyring", + }, + // do not set `EnvVars` for secret name flag + // To keep service account's permission minimum, the name of the secret is static across ALL applications. + // this value should only be set by admins of argocd as part of plugin setup + &cli.StringFlag{ + Name: "app-strongbox-secret-name", + Usage: `the value should be the name of a secret resource containing strongbox keyring used to +encrypt app secrets. name will be same across all applications`, + Value: "argocd-voodoobox-strongbox-keyring", + }, + + // SSH secrets flags + &cli.StringFlag{ + Name: "app-git-ssh-secret-namespace", + EnvVars: []string{argocdAppEnvPrefix + "GIT_SSH_SECRET_NAMESPACE"}, + Usage: `set 'GIT_SSH_SECRET_NAMESPACE' in argocd application as plugin ENV. the value should be the +name of a namespace where secret resource containing ssh keys are located`, + }, + // do not set `EnvVars` for secret name flag + // To keep service account's permission minimum, the name of the secret is static across ALL applications. + // this value should only be set by admins of argocd as part of plugin setup + &cli.StringFlag{ + Name: "app-git-ssh-secret-name", + Usage: `the value should be the name of a secret resource containing ssh keys used for +fetching remote kustomize bases from private repositories. name will be same across all applications`, + Value: "argocd-voodoobox-git-ssh", + }, } func main() { app := &cli.App{ Commands: []*cli.Command{ - // command to initialise application source directory - { - Name: "decrypt", - Usage: "command to decrypt all encrypted files under application source directory", - Flags: append(commonFlags, []cli.Flag{ - &cli.StringFlag{ - Name: "app-strongbox-secret-namespace", - EnvVars: []string{argocdAppEnvPrefix + "STRONGBOX_SECRET_NAMESPACE"}, - Usage: `set 'STRONGBOX_SECRET_NAMESPACE' in argocd application as plugin ENV. the value should be the - name of a namespace where secret resource containing strongbox keyring is located`, - }, - &cli.StringFlag{ - Name: "app-strongbox-secret-key", - EnvVars: []string{argocdAppEnvPrefix + "STRONGBOX_SECRET_KEY"}, - Usage: `set 'STRONGBOX_KEYRING_KEY' in argocd application as plugin ENV, the value should be the - name of the secret data key which contains a valid strongbox keyring file`, - Value: ".strongbox_keyring", - }, - // do not set `EnvVars` for secret name flag - // To keep service account's permission minimum, the name of the secret is static across ALL applications. - // this value should only be set by admins of argocd as part of plugin setup - &cli.StringFlag{ - Name: "app-strongbox-secret-name", - Usage: `the value should be the name of a secret resource containing strongbox keyring used to -encrypt app secrets. name will be same across all applications`, - Value: "argocd-voodoobox-strongbox-keyring", - }, - &cli.StringFlag{ - Name: "secret-allowed-namespaces-annotation", - Usage: `when shared secret is used this value is the annotation key to look for in secret - to get comma-separated list of all the namespaces that are allowed to use it`, - Destination: &secretAllowedNamespacesAnnotation, - Value: "argocd.voodoobox.plugin.io/allowed-namespaces", - }, - }...), - Action: func(c *cli.Context) error { - var err error - app := applicationInfo{ - name: c.String("app-name"), - destinationNamespace: c.String("app-namespace"), - } - app.keyringSecret = secretInfo{ - namespace: c.String("app-strongbox-secret-namespace"), - name: c.String("app-strongbox-secret-name"), - key: c.String("app-strongbox-secret-key"), - } - - kubeClient, err = getKubeClient() - if err != nil { - return fmt.Errorf("unable to create kube clienset err:%s", err) - } - - cwd, err := os.Getwd() - if err != nil { - return fmt.Errorf("unable to get current working dir err:%s", err) - } - - return ensureDecryption(c.Context, cwd, app) - }, - }, - - // command to generate manifests YAML { Name: "generate", - Usage: "generate will run kustomize build to generate kube manifests", - Flags: append(commonFlags, []cli.Flag{ - &cli.StringFlag{ - Name: "app-git-ssh-secret-namespace", - EnvVars: []string{argocdAppEnvPrefix + "GIT_SSH_SECRET_NAMESPACE"}, - Usage: `set 'GIT_SSH_SECRET_NAMESPACE' in argocd application as plugin ENV. the value should be the - name of a namespace where secret resource containing ssh keys are located`, - }, - // do not set `EnvVars` for secret name flag - // To keep service account's permission minimum, the name of the secret is static across ALL applications. - // this value should only be set by admins of argocd as part of plugin setup - &cli.StringFlag{ - Name: "app-git-ssh-secret-name", - Usage: `the value should be the name of a secret resource containing ssh keys used for -fetching remote kustomize bases from private repositories. name will be same across all applications`, - Value: "argocd-voodoobox-git-ssh", - }, - &cli.StringFlag{ - Name: "secret-allowed-namespaces-annotation", - Usage: `when shared secret is used this value is the annotation key to look for in secret - to get comma-separated list of all the namespaces that are allowed to use it`, - Destination: &secretAllowedNamespacesAnnotation, - Value: "argocd.voodoobox.plugin.io/allowed-namespaces", - }, - }...), + Usage: "generate will decrypt all strongbox encrypted file and then run kustomize build to generate kube manifests", + Flags: flags, Action: func(c *cli.Context) error { cwd, err := os.Getwd() if err != nil { @@ -171,11 +136,20 @@ fetching remote kustomize bases from private repositories. name will be same acr name: c.String("app-name"), destinationNamespace: c.String("app-namespace"), } + app.keyringSecret = secretInfo{ + namespace: c.String("app-strongbox-secret-namespace"), + name: c.String("app-strongbox-secret-name"), + key: c.String("app-strongbox-secret-key"), + } app.gitSSHSecret = secretInfo{ namespace: c.String("app-git-ssh-secret-namespace"), name: c.String("app-git-ssh-secret-name"), } + if err := ensureDecryption(c.Context, cwd, app); err != nil { + return err + } + manifests, err := ensureBuild(c.Context, cwd, app) if err != nil { return err diff --git a/secret.go b/secret.go index 7c47863..ca9b9ef 100644 --- a/secret.go +++ b/secret.go @@ -29,20 +29,20 @@ func getSecret(ctx context.Context, destNamespace string, secret secretInfo) (*v } // check if app's destination namespace is allowed on given secret resource - for _, v := range strings.Split(sec.Annotations[secretAllowedNamespacesAnnotation], ",") { + for _, v := range strings.Split(sec.Annotations[allowedNamespacesSecretAnnotation], ",") { if strings.TrimSpace(v) == destNamespace { return verifySecretEncrypted(sec) } } return nil, fmt.Errorf(`secret "%s/%s" cannot be used in namespace "%s", the destination namespace must be listed in the '%s' annotation`, - secret.namespace, secret.name, destNamespace, secretAllowedNamespacesAnnotation) + secret.namespace, secret.name, destNamespace, allowedNamespacesSecretAnnotation) } // isSecretEncrypted will go through all keys of the secret passed // and error out if at least one of them is encrypted -func verifySecretEncrypted (sec *v1.Secret) (*v1.Secret, error) { - for k, v := range sec.Data{ +func verifySecretEncrypted(sec *v1.Secret) (*v1.Secret, error) { + for k, v := range sec.Data { if bytes.HasPrefix(v, encryptedFilePrefix) { return nil, fmt.Errorf("secret %s/%s has an encrypted data for the key %s", sec.Namespace, sec.Name, k) } diff --git a/secret_test.go b/secret_test.go index a57f6db..6b69b8c 100644 --- a/secret_test.go +++ b/secret_test.go @@ -11,7 +11,7 @@ import ( ) func Test_getSecret(t *testing.T) { - secretAllowedNamespacesAnnotation = "argocd.voodoobox.plugin.io/allowed-namespaces" + allowedNamespacesSecretAnnotation = "argocd.voodoobox.plugin.io/allowed-namespaces" kubeClient = fake.NewSimpleClientset( &v1.Secret{ From 8b5a11a05ed3dfe91a1a8c53970569f726913550 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Wed, 9 Aug 2023 16:57:34 +0100 Subject: [PATCH 2/8] added global git ssh key support --- generate.go | 4 +-- git-ssh.go | 25 ++++++++++++---- git-ssh_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++---- main.go | 20 ++++++++++++- 4 files changed, 113 insertions(+), 14 deletions(-) diff --git a/generate.go b/generate.go index 907b3c2..501ffd4 100644 --- a/generate.go +++ b/generate.go @@ -11,7 +11,7 @@ import ( "strings" ) -func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, error) { +func ensureBuild(ctx context.Context, cwd string, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { // Even when there is no git SSH secret defined, we still override the // git ssh command (pointing the key to /dev/null) in order to avoid // using ssh keys in default system locations and to surface the error @@ -33,7 +33,7 @@ func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, } if hasRemoteBase { - sshCmdEnv, err = setupGitSSH(ctx, cwd, app) + sshCmdEnv, err = setupGitSSH(ctx, cwd, globalKeyPath, globalKnownHostFile, app) if err != nil { return "", err } diff --git a/git-ssh.go b/git-ssh.go index 7808b01..e304591 100644 --- a/git-ssh.go +++ b/git-ssh.go @@ -32,7 +32,7 @@ var ( reRepoURLWithSSH = regexp.MustCompile(`(?P^\s*-\s*(?:ssh:\/\/)?)(?P\w.+?@)?(?P\w.+?)(?P[\/:].*$)`) ) -func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, error) { +func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { knownHostsFragment := `-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no` sec, err := getSecret(ctx, app.destinationNamespace, app.gitSSHSecret) @@ -47,6 +47,7 @@ func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, // keyFilePaths holds key name and path values var keyFilePaths = make(map[string]string) + var userKnownHostFile string // write ssh data to ssh dir for k, v := range sec.Data { @@ -54,7 +55,7 @@ func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, if err := os.WriteFile(filepath.Join(sshDir, k), v, 0600); err != nil { return "", fmt.Errorf("unable to write known_hosts to temp file err:%s", err) } - knownHostsFragment = fmt.Sprintf(`-o UserKnownHostsFile=%s/known_hosts`, sshDir) + userKnownHostFile = sshDir + `/known_hosts` continue } // if key is not known_hosts then its assumed to be private keys @@ -78,7 +79,7 @@ func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, sshConfigFilename := filepath.Join(sshDir, "config") - body, err := constructSSHConfig(keyFilePaths, keyedDomain) + body, err := constructSSHConfig(keyFilePaths, keyedDomain, globalKeyPath) if err != nil { return "", err } @@ -86,6 +87,15 @@ func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, return "", err } + switch { + case globalKnownHostFile != "" && userKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile + ` -o UserKnownHostsFile=` + userKnownHostFile + case globalKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile + case userKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + userKnownHostFile + } + return fmt.Sprintf(`GIT_SSH_COMMAND=ssh -q -F %s %s`, sshConfigFilename, knownHostsFragment), nil } @@ -203,7 +213,7 @@ func replaceDomainWithConfigHostName(original string, keyName string) (string, s return newURL, domain, nil } -func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string) ([]byte, error) { +func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string, globalKeyPath string) ([]byte, error) { hostFragments := []string{} for keyName, domain := range keyedDomain { keyFilePath, ok := keyFilePaths[keyName] @@ -215,7 +225,12 @@ func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]s hostFragments = append(hostFragments, fmt.Sprintf(hostFragment, host, domain, keyFilePath)) } - if len(keyFilePaths) == 1 { + if globalKeyPath != "" { + hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, globalKeyPath)) + } + + // if global key is not provided and user provides only 1 key then also use that for all host + if len(keyFilePaths) == 1 && globalKeyPath == "" { for _, keyFilePath := range keyFilePaths { hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, keyFilePath)) } diff --git a/git-ssh_test.go b/git-ssh_test.go index 5b60a67..71c3208 100644 --- a/git-ssh_test.go +++ b/git-ssh_test.go @@ -284,6 +284,7 @@ func Test_constructSSHConfig(t *testing.T) { type args struct { keyFilePaths map[string]string keyedDomain map[string]string + globalKey string } tests := []struct { name string @@ -303,7 +304,22 @@ func Test_constructSSHConfig(t *testing.T) { IdentityFile path/to/this/key/key_a User git`}, false, - }, {"single-with-comment", + }, + {"single-with-globalKey", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + }, + keyedDomain: nil, + globalKey: "path/to/global/key", + }, + []string{`Host * + IdentitiesOnly yes + IdentityFile path/to/global/key + User git`}, + false, + }, + {"single-with-comment", args{ keyFilePaths: map[string]string{ "key_a": "path/to/this/key/key_a", @@ -320,6 +336,27 @@ func Test_constructSSHConfig(t *testing.T) { `Host * IdentitiesOnly yes IdentityFile path/to/this/key/key_a + User git`}, + false, + }, + {"single-with-comment-with-global-key", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + }, + keyedDomain: map[string]string{ + "key_a": "github.com", + }, + globalKey: "path/to/global/key", + }, + []string{`Host key_a_github_com + HostName github.com + IdentitiesOnly yes + IdentityFile path/to/this/key/key_a + User git`, + `Host * + IdentitiesOnly yes + IdentityFile path/to/global/key User git`}, false, }, @@ -357,6 +394,34 @@ func Test_constructSSHConfig(t *testing.T) { HostName github.com IdentitiesOnly yes IdentityFile path/to/this/key/keyD + User git`}, + false, + }, + {"multiple-keys-with-global-key", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + "sshKeyB": "path/to/this/key/sshKeyB", + }, + keyedDomain: map[string]string{ + "key_a": "github.com", + "sshKeyB": "gitlab.io", + }, + globalKey: "path/to/global/key", + }, + []string{`Host key_a_github_com + HostName github.com + IdentitiesOnly yes + IdentityFile path/to/this/key/key_a + User git`, + `Host sshKeyB_gitlab_io + HostName gitlab.io + IdentitiesOnly yes + IdentityFile path/to/this/key/sshKeyB + User git`, + `Host * + IdentitiesOnly yes + IdentityFile path/to/global/key User git`}, false, }, @@ -414,7 +479,7 @@ func Test_constructSSHConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain) + got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain, tt.args.globalKey) if (err != nil) != tt.wantErr { t.Errorf("constructSSHConfig() error = %v, wantErr %v", err, tt.wantErr) return @@ -423,8 +488,9 @@ func Test_constructSSHConfig(t *testing.T) { t.Errorf("constructSSHConfig() got unexpected output got = %s", got) return } - // since map is used to construct config it will be different all the time + for _, w := range tt.want { + // since map is used to construct config it will be different all the time if !bytes.Contains(got, []byte(w)) { t.Errorf("constructSSHConfig() %s\n\n ** missing from config **\n\n %s\n", w, got) } @@ -459,7 +525,7 @@ func Test_setupGitSSH(t *testing.T) { name: "argocd-voodoobox-git-ssh", }, } - _, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, withOutSecret) + _, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, "", "", withOutSecret) if err == nil { t.Fatal("expecting error here for missing secret from foo-bar NS") } @@ -472,8 +538,8 @@ func Test_setupGitSSH(t *testing.T) { }, } - wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" - env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, app) + wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=path/to/global/known_hosts -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" + env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, "path/to/global/key", "path/to/global/known_hosts", app) if err != nil { t.Fatal(err) } diff --git a/main.go b/main.go index 75bab64..9073381 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,20 @@ var flags = []cli.Flag{ Required: true, }, + // following flags/envs should be set by admin as part of plugin config + // Global SSH key + &cli.StringFlag{ + Name: "global-git-ssh-key-file", + EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KEY_FILE"}, + Value: "/etc/git-secret/ssh", + Usage: "The path to git ssh key which will be used to setup GIT_SSH_COMMAND env.", + }, + &cli.StringFlag{ + Name: "global-git-ssh-known-hosts-file", + EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KNOWN_HOSTS_FILE"}, + Value: "/etc/git-secret/known_hosts", + Usage: "The local path to the known hosts file used to setup GIT_SSH_COMMAND env.", + }, &cli.StringFlag{ Name: "allowed-namespaces-secret-annotation", EnvVars: []string{"AVP_ALLOWED_NS_SECRET_ANNOTATION"}, @@ -72,6 +86,7 @@ to get comma-separated list of all the namespaces that are allowed to use it`, Value: "argocd.voodoobox.plugin.io/allowed-namespaces", }, + // following envs comes from argocd application resource // strongbox secrets flags &cli.StringFlag{ Name: "app-strongbox-secret-namespace", @@ -132,6 +147,9 @@ func main() { return fmt.Errorf("unable to create kube clienset err:%s", err) } + globalKeyPath := c.String("global-git-ssh-key-file") + globalKnownHostFile := c.String("global-git-ssh-known-hosts-file") + app := applicationInfo{ name: c.String("app-name"), destinationNamespace: c.String("app-namespace"), @@ -150,7 +168,7 @@ func main() { return err } - manifests, err := ensureBuild(c.Context, cwd, app) + manifests, err := ensureBuild(c.Context, cwd, globalKeyPath, globalKnownHostFile, app) if err != nil { return err } From 1c8dd1968be2645e12d71d54ee8f554e1289cf1e Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Wed, 9 Aug 2023 17:14:50 +0100 Subject: [PATCH 3/8] updated go and dependencies --- Dockerfile | 2 +- go.mod | 24 +++++++++++------------ go.sum | 56 ++++++++++++++++++++++++++++++++---------------------- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2c7aa20..58564cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine AS build +FROM golang:1.21-alpine AS build ENV \ STRONGBOX_VERSION=1.1.0 \ diff --git a/go.mod b/go.mod index 291c257..ac6d5fc 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/utilitywarehouse/argocd-voodoobox-plugin -go 1.20 +go 1.21 require ( github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-hclog v1.5.0 github.com/urfave/cli/v2 v2.25.7 - k8s.io/api v0.28.0-alpha.3 - k8s.io/apimachinery v0.28.0-alpha.3 + k8s.io/api v0.29.0-alpha.0 + k8s.io/apimachinery v0.29.0-alpha.0 k8s.io/client-go v1.5.2 ) @@ -18,7 +18,7 @@ require ( github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fatih/color v1.15.0 // indirect github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -37,11 +37,11 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/net v0.11.0 // indirect - golang.org/x/oauth2 v0.9.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect @@ -49,10 +49,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230614213217-ba0abe644833 // indirect - k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect + k8s.io/kube-openapi v0.0.0-20230718181711-3c0fae5ee9fd // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 7a49691..4615c80 100644 --- a/go.sum +++ b/go.sum @@ -14,14 +14,16 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -37,6 +39,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= @@ -49,6 +52,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -72,15 +76,19 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= +github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -89,7 +97,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -106,10 +115,10 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= -golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -123,15 +132,15 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -139,6 +148,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -160,21 +170,21 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.28.0-alpha.3 h1:dl9ku8PmbvD7VFdgYzA34SRGsg5hpmDeqZUC670kVjw= -k8s.io/api v0.28.0-alpha.3/go.mod h1:8xUcnnu+0XlJiU0p9mwS7tRpUB1ce+XZrCYsEHeUQRw= -k8s.io/apimachinery v0.28.0-alpha.3 h1:poqReta738jpeYUyvxXxYbOk6hj5vc1EcKxyo0zhklk= -k8s.io/apimachinery v0.28.0-alpha.3/go.mod h1:tAiIbF8KB8+Ri2DfUWwZGwNOThIwM0fhXLnOymriu+4= +k8s.io/api v0.29.0-alpha.0 h1:U+fNSD93YX8KFeYu2mOsKP+AhktiEDw6VdvQ/pQN2kU= +k8s.io/api v0.29.0-alpha.0/go.mod h1:4nKPvoLiBp8GFtH8PBBiOu6dyLq1RQ6RLJvYwohLeQg= +k8s.io/apimachinery v0.29.0-alpha.0 h1:0hjEznQCsjbYaMG5uDQhoPtc0SBE+AdN88jmuIBuJUI= +k8s.io/apimachinery v0.29.0-alpha.0/go.mod h1:xhQIsaL3hXneGluH+0pzF7kr+VYuLS/VcYJxF1xQf+g= k8s.io/client-go v0.28.0-alpha.2 h1:Rheb29eRyFmcz7wtk17zVeaM2F7I8aO3wRbuMVydMb0= k8s.io/client-go v0.28.0-alpha.2/go.mod h1:1JNmAg+bItB9F82fAkt71E42k+yTaH3a+rJ0ggd9NqA= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230614213217-ba0abe644833 h1:mhSLxb0zA1QwoyF9cFPTpCYiVGBpFYuYxacJ1A9YGco= -k8s.io/kube-openapi v0.0.0-20230614213217-ba0abe644833/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20230718181711-3c0fae5ee9fd h1:0tN7VkdcfPGfii8Zl0edopOV08M6XxGlhO29AsPkBHw= +k8s.io/kube-openapi v0.0.0-20230718181711-3c0fae5ee9fd/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= +sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 7f084c8d54eea1bcbb390b13a6082b9c6a461581 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Mon, 14 Aug 2023 09:27:55 +0100 Subject: [PATCH 4/8] Revert "added global git ssh key support" This reverts commit 8b5a11a05ed3dfe91a1a8c53970569f726913550. --- generate.go | 4 +-- git-ssh.go | 25 ++++------------ git-ssh_test.go | 78 ++++--------------------------------------------- main.go | 20 +------------ 4 files changed, 14 insertions(+), 113 deletions(-) diff --git a/generate.go b/generate.go index 501ffd4..907b3c2 100644 --- a/generate.go +++ b/generate.go @@ -11,7 +11,7 @@ import ( "strings" ) -func ensureBuild(ctx context.Context, cwd string, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { +func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, error) { // Even when there is no git SSH secret defined, we still override the // git ssh command (pointing the key to /dev/null) in order to avoid // using ssh keys in default system locations and to surface the error @@ -33,7 +33,7 @@ func ensureBuild(ctx context.Context, cwd string, globalKeyPath, globalKnownHost } if hasRemoteBase { - sshCmdEnv, err = setupGitSSH(ctx, cwd, globalKeyPath, globalKnownHostFile, app) + sshCmdEnv, err = setupGitSSH(ctx, cwd, app) if err != nil { return "", err } diff --git a/git-ssh.go b/git-ssh.go index e304591..7808b01 100644 --- a/git-ssh.go +++ b/git-ssh.go @@ -32,7 +32,7 @@ var ( reRepoURLWithSSH = regexp.MustCompile(`(?P^\s*-\s*(?:ssh:\/\/)?)(?P\w.+?@)?(?P\w.+?)(?P[\/:].*$)`) ) -func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { +func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, error) { knownHostsFragment := `-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no` sec, err := getSecret(ctx, app.destinationNamespace, app.gitSSHSecret) @@ -47,7 +47,6 @@ func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHost // keyFilePaths holds key name and path values var keyFilePaths = make(map[string]string) - var userKnownHostFile string // write ssh data to ssh dir for k, v := range sec.Data { @@ -55,7 +54,7 @@ func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHost if err := os.WriteFile(filepath.Join(sshDir, k), v, 0600); err != nil { return "", fmt.Errorf("unable to write known_hosts to temp file err:%s", err) } - userKnownHostFile = sshDir + `/known_hosts` + knownHostsFragment = fmt.Sprintf(`-o UserKnownHostsFile=%s/known_hosts`, sshDir) continue } // if key is not known_hosts then its assumed to be private keys @@ -79,7 +78,7 @@ func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHost sshConfigFilename := filepath.Join(sshDir, "config") - body, err := constructSSHConfig(keyFilePaths, keyedDomain, globalKeyPath) + body, err := constructSSHConfig(keyFilePaths, keyedDomain) if err != nil { return "", err } @@ -87,15 +86,6 @@ func setupGitSSH(ctx context.Context, cwd string, globalKeyPath, globalKnownHost return "", err } - switch { - case globalKnownHostFile != "" && userKnownHostFile != "": - knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile + ` -o UserKnownHostsFile=` + userKnownHostFile - case globalKnownHostFile != "": - knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile - case userKnownHostFile != "": - knownHostsFragment = `-o UserKnownHostsFile=` + userKnownHostFile - } - return fmt.Sprintf(`GIT_SSH_COMMAND=ssh -q -F %s %s`, sshConfigFilename, knownHostsFragment), nil } @@ -213,7 +203,7 @@ func replaceDomainWithConfigHostName(original string, keyName string) (string, s return newURL, domain, nil } -func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string, globalKeyPath string) ([]byte, error) { +func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string) ([]byte, error) { hostFragments := []string{} for keyName, domain := range keyedDomain { keyFilePath, ok := keyFilePaths[keyName] @@ -225,12 +215,7 @@ func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]s hostFragments = append(hostFragments, fmt.Sprintf(hostFragment, host, domain, keyFilePath)) } - if globalKeyPath != "" { - hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, globalKeyPath)) - } - - // if global key is not provided and user provides only 1 key then also use that for all host - if len(keyFilePaths) == 1 && globalKeyPath == "" { + if len(keyFilePaths) == 1 { for _, keyFilePath := range keyFilePaths { hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, keyFilePath)) } diff --git a/git-ssh_test.go b/git-ssh_test.go index 71c3208..5b60a67 100644 --- a/git-ssh_test.go +++ b/git-ssh_test.go @@ -284,7 +284,6 @@ func Test_constructSSHConfig(t *testing.T) { type args struct { keyFilePaths map[string]string keyedDomain map[string]string - globalKey string } tests := []struct { name string @@ -304,22 +303,7 @@ func Test_constructSSHConfig(t *testing.T) { IdentityFile path/to/this/key/key_a User git`}, false, - }, - {"single-with-globalKey", - args{ - keyFilePaths: map[string]string{ - "key_a": "path/to/this/key/key_a", - }, - keyedDomain: nil, - globalKey: "path/to/global/key", - }, - []string{`Host * - IdentitiesOnly yes - IdentityFile path/to/global/key - User git`}, - false, - }, - {"single-with-comment", + }, {"single-with-comment", args{ keyFilePaths: map[string]string{ "key_a": "path/to/this/key/key_a", @@ -336,27 +320,6 @@ func Test_constructSSHConfig(t *testing.T) { `Host * IdentitiesOnly yes IdentityFile path/to/this/key/key_a - User git`}, - false, - }, - {"single-with-comment-with-global-key", - args{ - keyFilePaths: map[string]string{ - "key_a": "path/to/this/key/key_a", - }, - keyedDomain: map[string]string{ - "key_a": "github.com", - }, - globalKey: "path/to/global/key", - }, - []string{`Host key_a_github_com - HostName github.com - IdentitiesOnly yes - IdentityFile path/to/this/key/key_a - User git`, - `Host * - IdentitiesOnly yes - IdentityFile path/to/global/key User git`}, false, }, @@ -394,34 +357,6 @@ func Test_constructSSHConfig(t *testing.T) { HostName github.com IdentitiesOnly yes IdentityFile path/to/this/key/keyD - User git`}, - false, - }, - {"multiple-keys-with-global-key", - args{ - keyFilePaths: map[string]string{ - "key_a": "path/to/this/key/key_a", - "sshKeyB": "path/to/this/key/sshKeyB", - }, - keyedDomain: map[string]string{ - "key_a": "github.com", - "sshKeyB": "gitlab.io", - }, - globalKey: "path/to/global/key", - }, - []string{`Host key_a_github_com - HostName github.com - IdentitiesOnly yes - IdentityFile path/to/this/key/key_a - User git`, - `Host sshKeyB_gitlab_io - HostName gitlab.io - IdentitiesOnly yes - IdentityFile path/to/this/key/sshKeyB - User git`, - `Host * - IdentitiesOnly yes - IdentityFile path/to/global/key User git`}, false, }, @@ -479,7 +414,7 @@ func Test_constructSSHConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain, tt.args.globalKey) + got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain) if (err != nil) != tt.wantErr { t.Errorf("constructSSHConfig() error = %v, wantErr %v", err, tt.wantErr) return @@ -488,9 +423,8 @@ func Test_constructSSHConfig(t *testing.T) { t.Errorf("constructSSHConfig() got unexpected output got = %s", got) return } - + // since map is used to construct config it will be different all the time for _, w := range tt.want { - // since map is used to construct config it will be different all the time if !bytes.Contains(got, []byte(w)) { t.Errorf("constructSSHConfig() %s\n\n ** missing from config **\n\n %s\n", w, got) } @@ -525,7 +459,7 @@ func Test_setupGitSSH(t *testing.T) { name: "argocd-voodoobox-git-ssh", }, } - _, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, "", "", withOutSecret) + _, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, withOutSecret) if err == nil { t.Fatal("expecting error here for missing secret from foo-bar NS") } @@ -538,8 +472,8 @@ func Test_setupGitSSH(t *testing.T) { }, } - wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=path/to/global/known_hosts -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" - env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, "path/to/global/key", "path/to/global/known_hosts", app) + wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" + env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, app) if err != nil { t.Fatal(err) } diff --git a/main.go b/main.go index 9073381..75bab64 100644 --- a/main.go +++ b/main.go @@ -63,20 +63,6 @@ var flags = []cli.Flag{ Required: true, }, - // following flags/envs should be set by admin as part of plugin config - // Global SSH key - &cli.StringFlag{ - Name: "global-git-ssh-key-file", - EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KEY_FILE"}, - Value: "/etc/git-secret/ssh", - Usage: "The path to git ssh key which will be used to setup GIT_SSH_COMMAND env.", - }, - &cli.StringFlag{ - Name: "global-git-ssh-known-hosts-file", - EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KNOWN_HOSTS_FILE"}, - Value: "/etc/git-secret/known_hosts", - Usage: "The local path to the known hosts file used to setup GIT_SSH_COMMAND env.", - }, &cli.StringFlag{ Name: "allowed-namespaces-secret-annotation", EnvVars: []string{"AVP_ALLOWED_NS_SECRET_ANNOTATION"}, @@ -86,7 +72,6 @@ to get comma-separated list of all the namespaces that are allowed to use it`, Value: "argocd.voodoobox.plugin.io/allowed-namespaces", }, - // following envs comes from argocd application resource // strongbox secrets flags &cli.StringFlag{ Name: "app-strongbox-secret-namespace", @@ -147,9 +132,6 @@ func main() { return fmt.Errorf("unable to create kube clienset err:%s", err) } - globalKeyPath := c.String("global-git-ssh-key-file") - globalKnownHostFile := c.String("global-git-ssh-known-hosts-file") - app := applicationInfo{ name: c.String("app-name"), destinationNamespace: c.String("app-namespace"), @@ -168,7 +150,7 @@ func main() { return err } - manifests, err := ensureBuild(c.Context, cwd, globalKeyPath, globalKnownHostFile, app) + manifests, err := ensureBuild(c.Context, cwd, app) if err != nil { return err } From be5109536c2a0fcfae714701cc83e4dcd5572e71 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Mon, 14 Aug 2023 09:49:51 +0100 Subject: [PATCH 5/8] updated/fixed client-go version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ac6d5fc..6ceea73 100644 --- a/go.mod +++ b/go.mod @@ -57,4 +57,4 @@ require ( ) // https://github.com/kubernetes/client-go/issues/1084#issuecomment-1584750974 -replace k8s.io/client-go => k8s.io/client-go v0.28.0-alpha.2 +replace k8s.io/client-go => k8s.io/client-go v0.28.0-rc.1 diff --git a/go.sum b/go.sum index 4615c80..172f7be 100644 --- a/go.sum +++ b/go.sum @@ -174,8 +174,8 @@ k8s.io/api v0.29.0-alpha.0 h1:U+fNSD93YX8KFeYu2mOsKP+AhktiEDw6VdvQ/pQN2kU= k8s.io/api v0.29.0-alpha.0/go.mod h1:4nKPvoLiBp8GFtH8PBBiOu6dyLq1RQ6RLJvYwohLeQg= k8s.io/apimachinery v0.29.0-alpha.0 h1:0hjEznQCsjbYaMG5uDQhoPtc0SBE+AdN88jmuIBuJUI= k8s.io/apimachinery v0.29.0-alpha.0/go.mod h1:xhQIsaL3hXneGluH+0pzF7kr+VYuLS/VcYJxF1xQf+g= -k8s.io/client-go v0.28.0-alpha.2 h1:Rheb29eRyFmcz7wtk17zVeaM2F7I8aO3wRbuMVydMb0= -k8s.io/client-go v0.28.0-alpha.2/go.mod h1:1JNmAg+bItB9F82fAkt71E42k+yTaH3a+rJ0ggd9NqA= +k8s.io/client-go v0.28.0-rc.1 h1:VhPuRPoqm5vF7RMJ7Jw4u2ePm1Ut2TNgcfNzLh0sZZ4= +k8s.io/client-go v0.28.0-rc.1/go.mod h1:i94DLxjUXs5zrTMeOn3UHzGh4Wm47ajra+NZulANgJA= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230718181711-3c0fae5ee9fd h1:0tN7VkdcfPGfii8Zl0edopOV08M6XxGlhO29AsPkBHw= From a45e05fa4a5a04f0c1ebef57977466da294b4ee5 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Aug 2023 12:56:24 +0100 Subject: [PATCH 6/8] added global git ssh key support --- generate.go | 4 +- git-ssh.go | 88 +++++++++++++++++++++------------- git-ssh_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++---- main.go | 22 ++++++++- secret.go | 10 ++++ 5 files changed, 201 insertions(+), 46 deletions(-) diff --git a/generate.go b/generate.go index 907b3c2..722aae0 100644 --- a/generate.go +++ b/generate.go @@ -11,7 +11,7 @@ import ( "strings" ) -func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, error) { +func ensureBuild(ctx context.Context, cwd, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { // Even when there is no git SSH secret defined, we still override the // git ssh command (pointing the key to /dev/null) in order to avoid // using ssh keys in default system locations and to surface the error @@ -33,7 +33,7 @@ func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, } if hasRemoteBase { - sshCmdEnv, err = setupGitSSH(ctx, cwd, app) + sshCmdEnv, err = setupGitSSH(ctx, cwd, globalKeyPath, globalKnownHostFile, app) if err != nil { return "", err } diff --git a/git-ssh.go b/git-ssh.go index 7808b01..595ed40 100644 --- a/git-ssh.go +++ b/git-ssh.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" "io/fs" @@ -32,53 +33,58 @@ var ( reRepoURLWithSSH = regexp.MustCompile(`(?P^\s*-\s*(?:ssh:\/\/)?)(?P\w.+?@)?(?P\w.+?)(?P[\/:].*$)`) ) -func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, error) { +func setupGitSSH(ctx context.Context, cwd, globalKeyPath, globalKnownHostFile string, app applicationInfo) (string, error) { knownHostsFragment := `-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no` - sec, err := getSecret(ctx, app.destinationNamespace, app.gitSSHSecret) - if err != nil { - return "", fmt.Errorf("unable to get secret err:%v", err) - } - sshDir := filepath.Join(cwd, SSHDirName) if err := os.Mkdir(sshDir, 0700); err != nil { return "", fmt.Errorf("unable to create ssh config dir err:%s", err) } + sshConfigFilename := filepath.Join(sshDir, "config") // keyFilePaths holds key name and path values var keyFilePaths = make(map[string]string) + // keyFilePaths holds key name and domain it should be used for as values + var keyedDomain = make(map[string]string) + var userKnownHostFile string + + // attempt to get secret with user's own ssh keys if not found continue with just global key + sec, err := getSecret(ctx, app.destinationNamespace, app.gitSSHSecret) + if err != nil && !errors.Is(err, errNotFound) { + return "", err + } - // write ssh data to ssh dir - for k, v := range sec.Data { - if k == "known_hosts" { - if err := os.WriteFile(filepath.Join(sshDir, k), v, 0600); err != nil { - return "", fmt.Errorf("unable to write known_hosts to temp file err:%s", err) + if sec != nil { + // write ssh data to ssh dir + for k, v := range sec.Data { + if k == "known_hosts" { + if err := os.WriteFile(filepath.Join(sshDir, k), v, 0600); err != nil { + return "", fmt.Errorf("unable to write known_hosts to temp file err:%s", err) + } + userKnownHostFile = sshDir + `/known_hosts` + continue + } + // if key is not known_hosts then its assumed to be private keys + kfn := filepath.Join(sshDir, k) + // if the file containing the SSH key does not have a + // newline at the end, ssh does not complain about it but + // the key will not work properly + if !bytes.HasSuffix(v, []byte("\n")) { + v = append(v, byte('\n')) + } + keyFilePaths[k] = kfn + if err := os.WriteFile(kfn, v, 0600); err != nil { + return "", fmt.Errorf("unable to write key to temp file err:%s", err) } - knownHostsFragment = fmt.Sprintf(`-o UserKnownHostsFile=%s/known_hosts`, sshDir) - continue - } - // if key is not known_hosts then its assumed to be private keys - kfn := filepath.Join(sshDir, k) - // if the file containing the SSH key does not have a - // newline at the end, ssh does not complain about it but - // the key will not work properly - if !bytes.HasSuffix(v, []byte("\n")) { - v = append(v, byte('\n')) - } - keyFilePaths[k] = kfn - if err := os.WriteFile(kfn, v, 0600); err != nil { - return "", fmt.Errorf("unable to write key to temp file err:%s", err) } - } - keyedDomain, err := processKustomizeFiles(cwd) - if err != nil { - return "", fmt.Errorf("unable to updated kustomize files err:%s", err) + keyedDomain, err = processKustomizeFiles(cwd) + if err != nil { + return "", fmt.Errorf("unable to updated kustomize files err:%s", err) + } } - sshConfigFilename := filepath.Join(sshDir, "config") - - body, err := constructSSHConfig(keyFilePaths, keyedDomain) + body, err := constructSSHConfig(keyFilePaths, keyedDomain, globalKeyPath) if err != nil { return "", err } @@ -86,6 +92,15 @@ func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, return "", err } + switch { + case globalKnownHostFile != "" && userKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile + ` -o UserKnownHostsFile=` + userKnownHostFile + case globalKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + globalKnownHostFile + case userKnownHostFile != "": + knownHostsFragment = `-o UserKnownHostsFile=` + userKnownHostFile + } + return fmt.Sprintf(`GIT_SSH_COMMAND=ssh -q -F %s %s`, sshConfigFilename, knownHostsFragment), nil } @@ -203,7 +218,7 @@ func replaceDomainWithConfigHostName(original string, keyName string) (string, s return newURL, domain, nil } -func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string) ([]byte, error) { +func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string, globalKeyPath string) ([]byte, error) { hostFragments := []string{} for keyName, domain := range keyedDomain { keyFilePath, ok := keyFilePaths[keyName] @@ -215,7 +230,12 @@ func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]s hostFragments = append(hostFragments, fmt.Sprintf(hostFragment, host, domain, keyFilePath)) } - if len(keyFilePaths) == 1 { + if globalKeyPath != "" { + hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, globalKeyPath)) + } + + // if global key is not provided and user provides only 1 key then also use that for all host + if len(keyFilePaths) == 1 && globalKeyPath == "" { for _, keyFilePath := range keyFilePaths { hostFragments = append(hostFragments, fmt.Sprintf(singleKeyHostFragment, keyFilePath)) } diff --git a/git-ssh_test.go b/git-ssh_test.go index 5b60a67..09ad96c 100644 --- a/git-ssh_test.go +++ b/git-ssh_test.go @@ -284,6 +284,7 @@ func Test_constructSSHConfig(t *testing.T) { type args struct { keyFilePaths map[string]string keyedDomain map[string]string + globalKey string } tests := []struct { name string @@ -291,6 +292,27 @@ func Test_constructSSHConfig(t *testing.T) { want []string wantErr bool }{ + {"no-globalKey", + args{ + keyFilePaths: nil, + keyedDomain: nil, + globalKey: "", + }, + []string{``}, + false, + }, + {"only-globalKey", + args{ + keyFilePaths: nil, + keyedDomain: nil, + globalKey: "path/to/global/key", + }, + []string{`Host * + IdentitiesOnly yes + IdentityFile path/to/global/key + User git`}, + false, + }, {"single", args{ keyFilePaths: map[string]string{ @@ -303,7 +325,22 @@ func Test_constructSSHConfig(t *testing.T) { IdentityFile path/to/this/key/key_a User git`}, false, - }, {"single-with-comment", + }, + {"single-with-globalKey", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + }, + keyedDomain: nil, + globalKey: "path/to/global/key", + }, + []string{`Host * + IdentitiesOnly yes + IdentityFile path/to/global/key + User git`}, + false, + }, + {"single-with-comment", args{ keyFilePaths: map[string]string{ "key_a": "path/to/this/key/key_a", @@ -320,6 +357,27 @@ func Test_constructSSHConfig(t *testing.T) { `Host * IdentitiesOnly yes IdentityFile path/to/this/key/key_a + User git`}, + false, + }, + {"single-with-comment-with-global-key", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + }, + keyedDomain: map[string]string{ + "key_a": "github.com", + }, + globalKey: "path/to/global/key", + }, + []string{`Host key_a_github_com + HostName github.com + IdentitiesOnly yes + IdentityFile path/to/this/key/key_a + User git`, + `Host * + IdentitiesOnly yes + IdentityFile path/to/global/key User git`}, false, }, @@ -357,6 +415,34 @@ func Test_constructSSHConfig(t *testing.T) { HostName github.com IdentitiesOnly yes IdentityFile path/to/this/key/keyD + User git`}, + false, + }, + {"multiple-keys-with-global-key", + args{ + keyFilePaths: map[string]string{ + "key_a": "path/to/this/key/key_a", + "sshKeyB": "path/to/this/key/sshKeyB", + }, + keyedDomain: map[string]string{ + "key_a": "github.com", + "sshKeyB": "gitlab.io", + }, + globalKey: "path/to/global/key", + }, + []string{`Host key_a_github_com + HostName github.com + IdentitiesOnly yes + IdentityFile path/to/this/key/key_a + User git`, + `Host sshKeyB_gitlab_io + HostName gitlab.io + IdentitiesOnly yes + IdentityFile path/to/this/key/sshKeyB + User git`, + `Host * + IdentitiesOnly yes + IdentityFile path/to/global/key User git`}, false, }, @@ -414,7 +500,7 @@ func Test_constructSSHConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain) + got, err := constructSSHConfig(tt.args.keyFilePaths, tt.args.keyedDomain, tt.args.globalKey) if (err != nil) != tt.wantErr { t.Errorf("constructSSHConfig() error = %v, wantErr %v", err, tt.wantErr) return @@ -452,6 +538,7 @@ func Test_setupGitSSH(t *testing.T) { }, ) + // TEST1 application without secret should use global key withOutSecret := applicationInfo{ name: "app-foo", destinationNamespace: "foo-bar", @@ -459,11 +546,31 @@ func Test_setupGitSSH(t *testing.T) { name: "argocd-voodoobox-git-ssh", }, } - _, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, withOutSecret) - if err == nil { - t.Fatal("expecting error here for missing secret from foo-bar NS") + wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=path/to/global/known_hosts" + env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, "path/to/global/key", "path/to/global/known_hosts", withOutSecret) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(wnatEnv, env); diff != "" { + t.Errorf("setupGitSSH() mismatch (-want +got):\n%s", diff) + } + // Application should contain following folders and files.... + expectedFiles := []string{ + ".ssh", + ".ssh/config", + } + for _, name := range expectedFiles { + p := filepath.Join(withRemoteBaseTestDir, name) + _, err = os.Stat(p) + if err != nil { + t.Errorf("%s is missing, err:%s", p, err) + } } + // clear .ssh folder for next test + os.RemoveAll(filepath.Join(withRemoteBaseTestDir, ".ssh")) + + // TEST2 with user provided secret with ssh keys app := applicationInfo{ name: "app-foo", destinationNamespace: "foo", @@ -472,8 +579,8 @@ func Test_setupGitSSH(t *testing.T) { }, } - wnatEnv := "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" - env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, app) + wnatEnv = "GIT_SSH_COMMAND=ssh -q -F testData/app-with-remote-base-test1/.ssh/config -o UserKnownHostsFile=path/to/global/known_hosts -o UserKnownHostsFile=testData/app-with-remote-base-test1/.ssh/known_hosts" + env, err = setupGitSSH(context.Background(), withRemoteBaseTestDir, "path/to/global/key", "path/to/global/known_hosts", app) if err != nil { t.Fatal(err) } @@ -482,7 +589,7 @@ func Test_setupGitSSH(t *testing.T) { } // Application should contain following folders and files.... - expectedFiles := []string{ + expectedFiles = []string{ ".ssh", ".ssh/config", ".ssh/key_a", diff --git a/main.go b/main.go index 75bab64..da3e3c2 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,20 @@ var flags = []cli.Flag{ Required: true, }, + // following flags/envs should be set by admin as part of plugin config + // Global SSH key + &cli.StringFlag{ + Name: "global-git-ssh-key-file", + EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KEY_FILE"}, + Value: "/etc/git-secret/ssh", + Usage: "The path to git ssh key which will be used to setup GIT_SSH_COMMAND env.", + }, + &cli.StringFlag{ + Name: "global-git-ssh-known-hosts-file", + EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KNOWN_HOSTS_FILE"}, + Value: "/etc/git-secret/known_hosts", + Usage: "The local path to the known hosts file used to setup GIT_SSH_COMMAND env.", + }, &cli.StringFlag{ Name: "allowed-namespaces-secret-annotation", EnvVars: []string{"AVP_ALLOWED_NS_SECRET_ANNOTATION"}, @@ -72,6 +86,7 @@ to get comma-separated list of all the namespaces that are allowed to use it`, Value: "argocd.voodoobox.plugin.io/allowed-namespaces", }, + // following envs comes from argocd application resource // strongbox secrets flags &cli.StringFlag{ Name: "app-strongbox-secret-namespace", @@ -132,6 +147,9 @@ func main() { return fmt.Errorf("unable to create kube clienset err:%s", err) } + globalKeyPath := c.String("global-git-ssh-key-file") + globalKnownHostFile := c.String("global-git-ssh-known-hosts-file") + app := applicationInfo{ name: c.String("app-name"), destinationNamespace: c.String("app-namespace"), @@ -150,14 +168,14 @@ func main() { return err } - manifests, err := ensureBuild(c.Context, cwd, app) + manifests, err := ensureBuild(c.Context, cwd, globalKeyPath, globalKnownHostFile, app) if err != nil { return err } // argocd creates a temp folder of plugin which gets deleted // once plugin is existed still clean up secrets manually - // in case this behaviour changes + // in case this behavior changes os.Remove(filepath.Join(cwd, strongboxKeyRingFile)) os.RemoveAll(filepath.Join(cwd, SSHDirName)) diff --git a/secret.go b/secret.go index ca9b9ef..3f1a3fb 100644 --- a/secret.go +++ b/secret.go @@ -3,13 +3,17 @@ package main import ( "bytes" "context" + "errors" "fmt" "strings" v1 "k8s.io/api/core/v1" + kErrors "k8s.io/apimachinery/pkg/api/errors" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +var errNotFound = errors.New("not found") + // getSecret reads kube secret from either destination NS or secret's NS // if different NS is used then it will verify that dest NS is allowed to read the secret func getSecret(ctx context.Context, destNamespace string, secret secretInfo) (*v1.Secret, error) { @@ -18,6 +22,9 @@ func getSecret(ctx context.Context, destNamespace string, secret secretInfo) (*v if secret.namespace == "" || secret.namespace == destNamespace { sec, err := kubeClient.CoreV1().Secrets(destNamespace).Get(ctx, secret.name, metaV1.GetOptions{}) if err != nil { + if kErrors.IsNotFound(err) { + return nil, fmt.Errorf("unable to get secret %s/%s err:%w", destNamespace, secret.name, errNotFound) + } return nil, fmt.Errorf("unable to get secret %s/%s err:%s", destNamespace, secret.name, err) } return verifySecretEncrypted(sec) @@ -25,6 +32,9 @@ func getSecret(ctx context.Context, destNamespace string, secret secretInfo) (*v sec, err := kubeClient.CoreV1().Secrets(secret.namespace).Get(ctx, secret.name, metaV1.GetOptions{}) if err != nil { + if kErrors.IsNotFound(err) { + return nil, fmt.Errorf("unable to get secret %s/%s err:%w", destNamespace, secret.name, errNotFound) + } return nil, fmt.Errorf("unable to get secret %s/%s err:%s", secret.namespace, secret.name, err) } From b4500d96ca74302a52a2c1fdb73d2f10fbc25fe3 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Aug 2023 15:14:13 +0100 Subject: [PATCH 7/8] added global key info --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ea95e06..3e97baa 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,22 @@ An Argo CD plugin to decrypt strongbox encrypted files and build Kubernetes resources. plugin supports argocd version from 2.4 onwards and only same cluster deployments are supported. -This plugin has 2 commands +This plugin has 1 command -### `decrypt` -command will read kube secret containing keyring data and run strongbox decryption using this data. +### `generate` +generate command does following 2 things + +1) it will read kube secret containing keyring data and run strongbox decryption using this data. if multiple keys are used to encrypt app secrets then this secret should contain all the keys. -### `generate` -command will run kustomize build to generate kube resources's yaml strings. it will print this yaml stream to stdout. +2) command will run kustomize build to generate kube resources's yaml strings. it will print this yaml stream to stdout. + +#### private repository + +To fetch remote base from private repository, admin can add global ssh key which will be used for ALL applications. -Plugin support fetching remote base from private repositories, to do that user must create a secret with name `argocd-voodoobox-git-ssh`, + +user can also provide own ssh keys for an applications via secret with name `argocd-voodoobox-git-ssh`, that contains one or more SSH keys that provide access to the private repositories that contain these bases. To use an SSH key for Kustomize bases, the bases URL should be defined with the ssh:// scheme in kustomization.yaml and have a `# argocd-voodoobox-plugin: ` comment above it. if only 1 ssh key is used for ALL private repos then there is no need to specify this comment. @@ -128,22 +134,19 @@ data: allowConcurrency: true discover: fileName: "*" - init: - command: - - argocd-voodoobox-plugin - - decrypt - args: - - "--app-strongbox-secret-name=argocd-voodoobox-strongbox-keyring" - - "--secret-allowed-namespaces-annotation=argocd.voodoobox.plugin.io/allowed-namespaces" generate: command: - argocd-voodoobox-plugin - generate args: + - "--global-git-ssh-key-file=/path/to/global/key" + - "--global-git-ssh-known-hosts-file=/path/to/global/khf" + - "--app-strongbox-secret-name=argocd-voodoobox-strongbox-keyring" - "--app-git-ssh-secret-name=argocd-voodoobox-git-ssh" - - "--secret-allowed-namespaces-annotation=argocd.voodoobox.plugin.io/allowed-namespaces" + - "--allowed-namespaces-secret-annotation=argocd.voodoobox.plugin.io/allowed-namespaces" lockRepo: false ``` +* Instead of setting up arguments via configMap we can also set corresponding ENVs on plugin side car ### 2. patch `argocd-repo-server` deployment to add sidecar as shown volume from `cmp-plugin` configMap and mount it to `/home/argocd/cmp-server/config/plugin.yaml` @@ -228,7 +231,9 @@ subjects: | app arguments/ENVs | default | example / explanation | |-|-|-| -| --secret-allowed-namespaces-annotation | argocd.voodoobox.plugin.io/allowed-namespaces | when shared secret is used this value is the annotation key to look for in secret to get comma-separated list of all the namespaces that are allowed to use it | +| --allowed-namespaces-secret-annotation | argocd.voodoobox.plugin.io/allowed-namespaces | when shared secret is used this value is the annotation key to look for in secret to get comma-separated list of all the namespaces that are allowed to use it | +| --global-git-ssh-key-file | /etc/git-secret/ssh | The path to git ssh key file which will be used as global ssh key to fetch kustomize base from private repo for all application | +| --global-git-ssh-known-hosts-file | /etc/git-secret/known_hosts | The path to git known hosts file which will be used as with global ssh key to fetch kustomize base from private repo for all application | | --app-strongbox-secret-name | argocd-voodoobox-strongbox-keyring | the value should be the name of a secret resource containing strongbox keyring used to encrypt app secrets. name will be same across all applications | | --app-git-ssh-secret-name | argocd-voodoobox-git-ssh | the value should be the name of a secret resource containing ssh keys used for fetching remote kustomize bases from private repositories. name will be same across all applications | | ARGOCD_APP_NAME | set by argocd | name of application | From 2f4e665bf43df77f0c410db797691b0c42459f20 Mon Sep 17 00:00:00 2001 From: Ashok Siyani Date: Tue, 15 Aug 2023 15:36:21 +0100 Subject: [PATCH 8/8] global key flag should not have default to deletect if added by admin --- README.md | 4 ++-- main.go | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3e97baa..bd248a1 100644 --- a/README.md +++ b/README.md @@ -232,8 +232,8 @@ subjects: | app arguments/ENVs | default | example / explanation | |-|-|-| | --allowed-namespaces-secret-annotation | argocd.voodoobox.plugin.io/allowed-namespaces | when shared secret is used this value is the annotation key to look for in secret to get comma-separated list of all the namespaces that are allowed to use it | -| --global-git-ssh-key-file | /etc/git-secret/ssh | The path to git ssh key file which will be used as global ssh key to fetch kustomize base from private repo for all application | -| --global-git-ssh-known-hosts-file | /etc/git-secret/known_hosts | The path to git known hosts file which will be used as with global ssh key to fetch kustomize base from private repo for all application | +| --global-git-ssh-key-file | | The path to git ssh key file which will be used as global ssh key to fetch kustomize base from private repo for all application | +| --global-git-ssh-known-hosts-file | | The path to git known hosts file which will be used as with global ssh key to fetch kustomize base from private repo for all application | | --app-strongbox-secret-name | argocd-voodoobox-strongbox-keyring | the value should be the name of a secret resource containing strongbox keyring used to encrypt app secrets. name will be same across all applications | | --app-git-ssh-secret-name | argocd-voodoobox-git-ssh | the value should be the name of a secret resource containing ssh keys used for fetching remote kustomize bases from private repositories. name will be same across all applications | | ARGOCD_APP_NAME | set by argocd | name of application | diff --git a/main.go b/main.go index da3e3c2..9fe033a 100644 --- a/main.go +++ b/main.go @@ -68,14 +68,12 @@ var flags = []cli.Flag{ &cli.StringFlag{ Name: "global-git-ssh-key-file", EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KEY_FILE"}, - Value: "/etc/git-secret/ssh", - Usage: "The path to git ssh key which will be used to setup GIT_SSH_COMMAND env.", + Usage: "The path to git ssh key file which will be used as global ssh key to fetch kustomize base from private repo for all application", }, &cli.StringFlag{ Name: "global-git-ssh-known-hosts-file", EnvVars: []string{"AVP_GLOBAL_GIT_SSH_KNOWN_HOSTS_FILE"}, - Value: "/etc/git-secret/known_hosts", - Usage: "The local path to the known hosts file used to setup GIT_SSH_COMMAND env.", + Usage: "The path to git known hosts file which will be used as with global ssh key to fetch kustomize base from private repo for all application", }, &cli.StringFlag{ Name: "allowed-namespaces-secret-annotation",