Skip to content

Commit

Permalink
Merge pull request #5 from utilitywarehouse/as-key-ref
Browse files Browse the repository at this point in the history
make secret name static and check for ssh:// schema
  • Loading branch information
asiyani authored Oct 26, 2022
2 parents cb4f460 + c5b8c99 commit 6c6928d
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 56 deletions.
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ if multiple keys are used to encrypt app secrets then this secret should contain
### `generate`
command will run kustomize build to generate kube resources's yaml strings. it will print this yaml stream to stdout.

You can specify custom SSH keys to be used for fetching remote kustomize bases from private repositories. In order to do that,
you will need to set `GIT_SSH_SECRET_NAME` plugin env, it should reference a Secret name that contains one or more SSH keys
that provide access to the private repositories that contain these bases. if this env is not set then plugin will
only be able to fetch remote bases from open repositories.
Plugin support fetching remote base from private repositories, to do that user must create a 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: <key_file_name>` comment above it.
if only 1 ssh key is used for ALL private repos then there is no need to specify this comment.

To use an SSH key for Kustomize bases, the bases should be defined with the ssh:// scheme in kustomization.yaml and have a
`# argocd-voodoobox-plugin: key_foobar` comment above it. For example:
If `ssh://` is not used then plugin will assume only public repos are used and it will skip ssh config setup.

```yaml
resources:
Expand All @@ -39,7 +38,7 @@ resources:
Plugin supports following _plugin envs_ which can be set in ArgoCD Application crd
`STRONGBOX_SECRET_NAME` the value should be the name of a secret resource containing strongbox keyring used to encrypt app secrets. the default value is `argocd-strongbox-keyring`
The value of name of a secret resource containing strongbox keyring used to encrypt app secrets, must be `argocd-voodoobox-strongbox-keyring`.

`STRONGBOX_SECRET_KEY` the value should be the name of the secret data key which contains a valid strongbox keyring file data. the default value is `.strongbox_keyring`

Expand All @@ -54,7 +53,7 @@ If this env is not specified then it defaults to the same namespace as the app's
kind: Secret
apiVersion: v1
metadata:
name: argocd-strongbox-keyring
name: argocd-voodoobox-strongbox-keyring
namespace: ns-a
annotations:
argocd.voodoobox.plugin.io/allowed-namespaces: "ns-b, ns-c"
Expand All @@ -76,25 +75,20 @@ spec:
env:
- name: STRONGBOX_SECRET_NAMESPACE
value: team-a
- name: STRONGBOX_SECRET_NAME
value: argocd-strongbox-keyring
- name: STRONGBOX_SECRET_KEY
value: .strongbox_keyring
```

### Git SSH Keys Envs

`GIT_SSH_SECRET_NAME` the value should be the name of a secret resource containing ssh keys used for fetching remote kustomize bases from private repositories. Additionally this Secret can optionally define a value for "known_hosts". If omitted, git will use ssh with StrictHostKeyChecking disabled. There is no default value for this env it must be set if repo base contains remote
private bases.

`GIT_SSH_SECRET_NAMESPACE` the value should be the name of a namespace where secret resource containing ssh keys are located. If this env is not specified then it defaults to the same namespace as the app's destination NS.
the Secret should have an annotation called "argocd.voodoobox.plugin.io/allowed-namespaces" which contains a comma-separated list of all the namespaces that are allowed to use it.

```yaml
kind: Secret
apiVersion: v1
metadata:
name: argocd-git-ssh
name: argocd-voodoobox-git-ssh
namespace: ns-a
annotations:
kube-applier.io/allowed-namespaces: "ns-b, ns-c"
Expand Down Expand Up @@ -139,12 +133,14 @@ data:
- 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:
- "--app-git-ssh-secret-name=argocd-voodoobox-git-ssh"
- "--secret-allowed-namespaces-annotation=argocd.voodoobox.plugin.io/allowed-namespaces"
lockRepo: false
```
Expand Down Expand Up @@ -204,6 +200,9 @@ metadata:
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames:
- argocd-voodoobox-strongbox-keyring
- argocd-voodoobox-git-ssh
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
Expand All @@ -230,10 +229,12 @@ 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 |
| --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 |
| ARGOCD_APP_NAMESPACE | set by argocd | application's destination namespace |
| STRONGBOX_SECRET_NAME¹ | argocd-strongbox-keyring | the name of a secret resource containing strongbox keyring used to encrypt app secrets |
| STRONGBOX_KEYRING_KEY¹ | .strongbox_keyring | the name of the secret data key which contains a valid strongbox keyring file |
| STRONGBOX_SECRET_NAMESPACE¹ | | the name of a namespace where secret resource containing strongbox keyring is located |
| GIT_SSH_SECRET_NAMESPACE¹ | | the value should be the name of a namespace where secret resource containing ssh keys are located |

¹ These ENVs should be added to argocd application plugin env sections
67 changes: 62 additions & 5 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,85 @@ import (
"bytes"
"context"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
)

func ensureBuild(ctx context.Context, cwd string, app applicationInfo) (string, error) {
c, err := setupGitSSH(ctx, cwd, app)
// 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
// if bases over ssh have been configured.
sshCmdEnv := `GIT_SSH_COMMAND=ssh -q -F none -o IdentitiesOnly=yes -o IdentityFile=/dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`

kFiles, err := findKustomizeFiles(cwd)
if err != nil {
return "", err
return "", fmt.Errorf("unable to ge kustomize files paths err:%s", err)
}

if len(kFiles) == 0 {
return "", fmt.Errorf("only kustomize base is supported")
}

//
env := os.Environ()
hasRemoteBase, err := hasSSHRemoteBaseURL(kFiles)
if err != nil {
return "", fmt.Errorf("unable to look for ssh protocol err:%s", err)
}

if hasRemoteBase {
sshCmdEnv, err = setupGitSSH(ctx, cwd, app)
if err != nil {
return "", err
}
}

// setup env for Kustomize command
env := []string{
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
}

env = append(env, c)
env = append(env, sshCmdEnv)
// Set HOME to cwd, this means that SSH should not pick up any
// local SSH keys and use them for cloning
env = append(env, fmt.Sprintf("HOME=%s", cwd))

return runKustomizeBuild(ctx, cwd, env)
}

func findKustomizeFiles(cwd string) ([]string, error) {
kFiles := []string{}

err := filepath.WalkDir(cwd, func(path string, info fs.DirEntry, err error) error {
if filepath.Base(path) == "kustomization.yaml" ||
filepath.Base(path) == "kustomization.yml" ||
filepath.Base(path) == "Kustomization" {
kFiles = append(kFiles, path)
}
return nil
})
if err != nil {
return nil, err
}

return kFiles, nil
}

func hasSSHRemoteBaseURL(kFiles []string) (bool, error) {
for _, k := range kFiles {
data, err := os.ReadFile(k)
if err != nil {
return false, err
}
if bytes.Contains(data, []byte("ssh://")) {
return true, nil
}
}
return false, nil
}

// runKustomizeBuild will run `kustomize build` cmd and return generated yaml or error
func runKustomizeBuild(ctx context.Context, cwd string, env []string) (string, error) {
k := exec.CommandContext(ctx, "kustomize", "build", ".")
Expand Down
35 changes: 35 additions & 0 deletions generate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import "testing"

func Test_hasSSHRemoteBaseURL(t *testing.T) {
type args struct {
cwd string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{"with remote base", args{"./testData/app-with-remote-base"}, true, false},
{"without remote base", args{"./testData/app-with-secrets"}, false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

files, err := findKustomizeFiles(tt.args.cwd)
if err != nil {
t.Fatal(err)
}
got, err := hasSSHRemoteBaseURL(files)
if (err != nil) != tt.wantErr {
t.Errorf("hasSSHRemoteBaseURL() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("hasSSHRemoteBaseURL() = %v, want %v", got, tt.want)
}
})
}
}
14 changes: 2 additions & 12 deletions git-ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,16 @@ var (
)

func setupGitSSH(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
// if bases over ssh have been configured.
sshCmd := `GIT_SSH_COMMAND=ssh -q -F none -o IdentitiesOnly=yes -o IdentityFile=/dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`
knownHostsFragment := `-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`

// if ssh secret name is not set skip setup
if app.gitSSHSecret.name == "" {
return sshCmd, nil
}

sec, err := getSecret(ctx, app.destinationNamespace, app.gitSSHSecret)
if err != nil {
return sshCmd, fmt.Errorf("unable to get secret err:%v", err)
return "", fmt.Errorf("unable to get secret err:%v", err)
}

sshDir := filepath.Join(cwd, ".ssh")
if err := os.Mkdir(sshDir, 0700); err != nil {
return sshCmd, fmt.Errorf("unable to create ssh config dir err:%s", err)
return "", fmt.Errorf("unable to create ssh config dir err:%s", err)
}

// keyFilePaths holds key name and path values
Expand Down
24 changes: 11 additions & 13 deletions git-ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func Test_setupGitSSH(t *testing.T) {
kubeClient = fake.NewSimpleClientset(
&v1.Secret{
ObjectMeta: metaV1.ObjectMeta{
Name: "argocd-git-ssh",
Name: "argocd-voodoobox-git-ssh",
Namespace: "foo",
},
Data: map[string][]byte{
Expand All @@ -352,30 +352,28 @@ func Test_setupGitSSH(t *testing.T) {
},
)

noRemoteBase := applicationInfo{
withOutSecret := applicationInfo{
name: "app-foo",
destinationNamespace: "foo",
}

defaultEnv := "GIT_SSH_COMMAND=ssh -q -F none -o IdentitiesOnly=yes -o IdentityFile=/dev/null -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, noRemoteBase)
if err != nil {
t.Fatal(err)
destinationNamespace: "foo-bar",
gitSSHSecret: secretInfo{
name: "argocd-voodoobox-git-ssh",
},
}
if diff := cmp.Diff(defaultEnv, env); diff != "" {
t.Errorf("setupGitSSH() mismatch (-want +got):\n%s", diff)
_, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, withOutSecret)
if err == nil {
t.Fatal("expecting error here for missing secret from foo-bar NS")
}

app := applicationInfo{
name: "app-foo",
destinationNamespace: "foo",
gitSSHSecret: secretInfo{
name: "argocd-git-ssh",
name: "argocd-voodoobox-git-ssh",
},
}

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)
env, err := setupGitSSH(context.Background(), withRemoteBaseTestDir, app)
if err != nil {
t.Fatal(err)
}
Expand Down
27 changes: 16 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,22 @@ func main() {
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-name",
EnvVars: []string{argocdAppEnvPrefix + "STRONGBOX_SECRET_NAME"},
Usage: `set 'STRONGBOX_SECRET_NAME' in argocd application as plugin ENV. the value should be the
name of a secret resource containing strongbox keyring used to encrypt app secrets`,
Value: "argocd-strongbox-keyring",
},
&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
Expand Down Expand Up @@ -133,11 +135,14 @@ func main() {
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",
EnvVars: []string{argocdAppEnvPrefix + "GIT_SSH_SECRET_NAME"},
Usage: `set 'GIT_SSH_SECRET_NAME' in argocd application as plugin ENV. the value should be the
name of a secret resource containing ssh keys used for fetching remote kustomize bases from private repositories`,
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",
Expand Down

0 comments on commit 6c6928d

Please sign in to comment.