Skip to content

Commit

Permalink
Add new -token-sink-file flag
Browse files Browse the repository at this point in the history
If this flag is set and -init-type=sync then we will write the acl token
to that file.
  • Loading branch information
lkysow committed Mar 16, 2020
1 parent 19f8701 commit 05b6bb9
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 18 deletions.
49 changes: 31 additions & 18 deletions subcommand/acl-init/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand All @@ -61,23 +64,25 @@ func (c *Command) Run(args []string) int {
return 1
}

config, err := subcommand.K8SConfig(c.k8s.KubeConfig())
if err != nil {
c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err))
return 1
}

// 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 {
config, err := subcommand.K8SConfig(c.k8s.KubeConfig())
if err != nil {
c.UI.Error(fmt.Sprintf("Error retrieving Kubernetes auth: %s", err))
return 1
}
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
// If not, wait until it does
var secret string
for {
var err error
secret, err = c.getSecret(c.flagSecretName)
if err != nil {
c.UI.Error(fmt.Sprintf("Error getting Kubernetes secret: %s", err))
Expand All @@ -93,7 +98,7 @@ func (c *Command) Run(args []string) int {
// This will be mounted as a volume for the client to use
var buf bytes.Buffer
tpl := template.Must(template.New("root").Parse(strings.TrimSpace(clientACLConfigTpl)))
err = tpl.Execute(&buf, secret)
err := tpl.Execute(&buf, secret)
if err != nil {
c.UI.Error(fmt.Sprintf("Error creating template: %s", err))
return 1
Expand All @@ -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
}

Expand Down
89 changes: 89 additions & 0 deletions subcommand/acl-init/command_test.go
Original file line number Diff line number Diff line change
@@ -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`,
)
}

0 comments on commit 05b6bb9

Please sign in to comment.