From fe5948924adbdc4b2db3382d878068ca237c2ab5 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Mon, 16 Mar 2020 11:57:24 -0700 Subject: [PATCH] Add new -token-sink-file flag If this flag is set and -init-type=sync then we will write the acl token to that file. --- subcommand/acl-init/command.go | 35 ++++++++---- subcommand/acl-init/command_test.go | 89 +++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 subcommand/acl-init/command_test.go diff --git a/subcommand/acl-init/command.go b/subcommand/acl-init/command.go index cd62c9de60..5ec735cd88 100644 --- a/subcommand/acl-init/command.go +++ b/subcommand/acl-init/command.go @@ -22,14 +22,15 @@ import ( type Command struct { UI cli.Ui - flags *flag.FlagSet - k8s *k8sflags.K8SFlags - flagSecretName string - flagInitType string - flagNamespace string - flagACLDir string + flags *flag.FlagSet + k8s *k8sflags.K8SFlags + flagSecretName string + flagInitType string + flagNamespace string + flagACLDir string + flagTokenSinkFile string - k8sClient *kubernetes.Clientset + k8sClient kubernetes.Interface once sync.Once help string @@ -45,6 +46,8 @@ func (c *Command) init() { "Name of Kubernetes namespace where the servers are deployed") c.flags.StringVar(&c.flagACLDir, "acl-dir", "/consul/aclconfig", "Directory name of shared volume where acl config will be output") + c.flags.StringVar(&c.flagTokenSinkFile, "token-sink-file", "", + "Filepath to write acl token to when -init-type is sync") c.k8s = &k8sflags.K8SFlags{} flags.Merge(c.flags, c.k8s.Flags()) @@ -68,10 +71,12 @@ func (c *Command) Run(args []string) int { } // Create the Kubernetes clientset - c.k8sClient, err = kubernetes.NewForConfig(config) - if err != nil { - c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) - return 1 + if c.k8sClient == nil { + c.k8sClient, err = kubernetes.NewForConfig(config) + if err != nil { + c.UI.Error(fmt.Sprintf("Error initializing Kubernetes client: %s", err)) + return 1 + } } // Check if the client secret exists yet @@ -107,6 +112,14 @@ func (c *Command) Run(args []string) int { } } + if c.flagInitType == "sync" && c.flagTokenSinkFile != "" { + err = ioutil.WriteFile(c.flagTokenSinkFile, []byte(secret), 0400) + if err != nil { + c.UI.Error(fmt.Sprintf("Error writing token to file %q: %s", c.flagTokenSinkFile, err)) + return 1 + } + } + return 0 } diff --git a/subcommand/acl-init/command_test.go b/subcommand/acl-init/command_test.go new file mode 100644 index 0000000000..0b6f54ffa0 --- /dev/null +++ b/subcommand/acl-init/command_test.go @@ -0,0 +1,89 @@ +package aclinit + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/cli" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +// Test that we write the secret data to a file. +func TestRun_TokenSinkFile(t *testing.T) { + t.Parallel() + require := require.New(t) + tmpDir, err := ioutil.TempDir("", "") + require.NoError(err) + defer os.Remove(tmpDir) + + // Set up k8s with the secret. + token := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8sNS := "default" + secretName := "secret-name" + k8s := fake.NewSimpleClientset() + k8s.CoreV1().Secrets(k8sNS).Create(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Data: map[string][]byte{ + "token": []byte(token), + }, + }) + + sinkFile := filepath.Join(tmpDir, "acl-token") + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: k8s, + } + code := cmd.Run([]string{ + "-k8s-namespace", k8sNS, + "-init-type=sync", + "-token-sink-file", sinkFile, + }) + require.Equal(0, code, ui.ErrorWriter.String()) + + bytes, err := ioutil.ReadFile(sinkFile) + require.NoError(err) + require.Equal(token, string(bytes), "exp: %s, got: %s", token, string(bytes)) +} + +// Test that if there's an error writing the sink file it's returned. +func TestRun_TokenSinkFileErr(t *testing.T) { + t.Parallel() + require := require.New(t) + + // Set up k8s with the secret. + token := "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + k8sNS := "default" + secretName := "secret-name" + k8s := fake.NewSimpleClientset() + k8s.CoreV1().Secrets(k8sNS).Create(&v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Data: map[string][]byte{ + "token": []byte(token), + }, + }) + + ui := cli.NewMockUi() + cmd := Command{ + UI: ui, + k8sClient: k8s, + } + code := cmd.Run([]string{ + "-k8s-namespace", k8sNS, + "-init-type=sync", + "-token-sink-file", "/this/filepath/does/not/exist", + }) + require.Equal(1, code) + require.Contains(ui.ErrorWriter.String(), + `Error writing token to file "/this/filepath/does/not/exist": open /this/filepath/does/not/exist: no such file or directory`, + ) +}