Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add -token-sink-file flag to acl-init #232

Merged
merged 1 commit into from
Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 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 @@ -40,11 +41,13 @@ func (c *Command) init() {
c.flags.StringVar(&c.flagSecretName, "secret-name", "",
"Name of secret to watch for an ACL token")
c.flags.StringVar(&c.flagInitType, "init-type", "",
"ACL init target, valid values are `client` and `sync`")
"ACL init type. The only supported value is 'client'. If set to 'client' will write Consul client ACL config to an acl-config.json file in -acl-dir")
c.flags.StringVar(&c.flagNamespace, "k8s-namespace", "",
"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")
"Directory name of shared volume where client acl config file acl-config.json will be written if -init-type=client")
c.flags.StringVar(&c.flagTokenSinkFile, "token-sink-file", "",
"Optional filepath to write acl token")

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.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
87 changes: 87 additions & 0 deletions subcommand/acl-init/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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.RemoveAll(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,
"-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,
"-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`,
)
}