Skip to content

feat: Enable caching tokens for multiple vault namespaces #695

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
84 changes: 84 additions & 0 deletions cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,90 @@ func TestMain(t *testing.T) {
}
})

t.Run("will create cache per namespace when provided", func(t *testing.T) {

// Purging token cache before launching this test
err := utils.PurgeTokenCache()
if err != nil {
t.Fatalf("fail to purge tocken cache: %s", err.Error())
}

args := []string{"../fixtures/input/nonempty"}
cmd := NewGenerateCommand()

b := bytes.NewBufferString("")
e := bytes.NewBufferString("")
cmd.SetArgs(args)
cmd.SetOut(b)
cmd.SetErr(e)
cmd.Execute()
out, err := io.ReadAll(b) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}
stderr, err := io.ReadAll(e) // Read buffer to bytes
if err != nil {
t.Fatal(err)
}

buf, err := os.ReadFile("../fixtures/output/all.yaml")
if err != nil {
t.Fatal(err)
}

// We first check that the command was successful to make sure it reached the token caching part
expected := string(buf)
if string(out) != expected {
t.Fatalf("expected %s\n\nbut got\n\n%s\nerr: %s", expected, string(out), string(stderr))
}

// Default namespace cache is expected
_, err = utils.ReadExistingToken(fmt.Sprintf("approle_default_%s", roleid))
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}

// Setting the vault namespace environment variable
os.Setenv("VAULT_NAMESPACE", `test-namespace`)

nsArgs := []string{"../fixtures/input/nonempty"}
nsCmd := NewGenerateCommand()

nsB := bytes.NewBufferString("")
nsE := bytes.NewBufferString("")
nsCmd.SetArgs(nsArgs)
nsCmd.SetOut(nsB)
nsCmd.SetErr(nsE)
nsCmd.Execute()
nsOut, nsErr := io.ReadAll(nsB) // Read buffer to bytes
if nsErr != nil {
t.Fatal(nsErr)
}
nsStderr, nsErr := io.ReadAll(nsE) // Read buffer to bytes
if nsErr != nil {
t.Fatal(nsErr)
}

nsBuf, nsErr := os.ReadFile("../fixtures/output/all.yaml")
if nsErr != nil {
t.Fatal(nsErr)
}

// We first check that the command was successful to make sure it reached the token caching part
nsExpected := string(nsBuf)
if string(nsOut) != nsExpected {
t.Fatalf("expected %s\n\nbut got\n\n%s\nerr: %s", nsExpected, string(nsOut), string(nsStderr))
}

// Namespaced cache is expected
_, nsErr = utils.ReadExistingToken(fmt.Sprintf("approle_test-namespace_%s", roleid))
if nsErr != nil {
t.Fatalf("expected cached vault token but got: %s", nsErr)
}

os.Unsetenv("VAULT_NAMESPACE")
})

os.Unsetenv("AVP_TYPE")
os.Unsetenv("VAULT_ADDR")
os.Unsetenv("AVP_AUTH_TYPE")
Expand Down
8 changes: 5 additions & 3 deletions pkg/auth/vault/approle.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ type AppRoleAuth struct {
RoleID string
SecretID string
MountPath string
Namespace string
}

// NewAppRoleAuth initalizes a new AppRolAuth with role id and secret id
func NewAppRoleAuth(roleID, secretID, mountPath string) *AppRoleAuth {
func NewAppRoleAuth(roleID, secretID, mountPath string, namespace string) *AppRoleAuth {
appRoleAuth := &AppRoleAuth{
RoleID: roleID,
SecretID: secretID,
MountPath: approleMountPath,
Namespace: namespace,
}
if mountPath != "" {
appRoleAuth.MountPath = mountPath
Expand All @@ -33,7 +35,7 @@ func NewAppRoleAuth(roleID, secretID, mountPath string) *AppRoleAuth {

// Authenticate authenticates with Vault using App Role and returns a token
func (a *AppRoleAuth) Authenticate(vaultClient *api.Client) error {
err := utils.LoginWithCachedToken(vaultClient, fmt.Sprintf("approle_%s", a.RoleID))
err := utils.LoginWithCachedToken(vaultClient, fmt.Sprintf("approle_%s_%s", a.Namespace, a.RoleID))
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot retrieve cached token: %v. Generating a new one", err)
} else {
Expand All @@ -54,7 +56,7 @@ func (a *AppRoleAuth) Authenticate(vaultClient *api.Client) error {
utils.VerboseToStdErr("Hashicorp Vault authentication response: %v", data)

// If we cannot write the Vault token, we'll just have to login next time. Nothing showstopping.
err = utils.SetToken(vaultClient, fmt.Sprintf("approle_%s", a.RoleID), data.Auth.ClientToken)
err = utils.SetToken(vaultClient, fmt.Sprintf("approle_%s_%s", a.Namespace, a.RoleID), data.Auth.ClientToken)
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot cache token for future runs: %v", err)
}
Expand Down
26 changes: 21 additions & 5 deletions pkg/auth/vault/approle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ func TestAppRoleLogin(t *testing.T) {
cluster, roleID, secretID := helpers.CreateTestAppRoleVault(t)
defer cluster.Cleanup()

appRole := vault.NewAppRoleAuth(roleID, secretID, "")
appRole := vault.NewAppRoleAuth(roleID, secretID, "", "default")

err := appRole.Authenticate(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_%s", roleID))
cachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_default_%s", roleID))
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -31,7 +31,7 @@ func TestAppRoleLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_%s", roleID))
newCachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_default_%s", roleID))
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -44,14 +44,14 @@ func TestAppRoleLogin(t *testing.T) {
secondCluster, secondRoleID, secondSecretID := helpers.CreateTestAppRoleVault(t)
defer secondCluster.Cleanup()

secondAppRole := vault.NewAppRoleAuth(secondRoleID, secondSecretID, "")
secondAppRole := vault.NewAppRoleAuth(secondRoleID, secondSecretID, "", "default")

err = secondAppRole.Authenticate(secondCluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

secondCachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_%s", secondRoleID))
secondCachedToken, err := utils.ReadExistingToken(fmt.Sprintf("approle_default_%s", secondRoleID))
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -60,4 +60,20 @@ func TestAppRoleLogin(t *testing.T) {
if bytes.Compare(cachedToken, secondCachedToken) == 0 {
t.Fatalf("expected different tokens but got %s", secondCachedToken)
}

// We create a new connection to a specific namespace and create a different cache
namespaceCluster, namespaceRoleID, namespaceSecretID := helpers.CreateTestAppRoleVault(t)
defer namespaceCluster.Cleanup()

namespaceAppRole := vault.NewAppRoleAuth(namespaceRoleID, namespaceSecretID, "", "my-other-namespace")

err = namespaceAppRole.Authenticate(namespaceCluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

_, err = utils.ReadExistingToken(fmt.Sprintf("approle_my-other-namespace_%s", namespaceRoleID))
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
}
8 changes: 5 additions & 3 deletions pkg/auth/vault/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ const (
type GithubAuth struct {
AccessToken string
MountPath string
Namespace string
}

// NewGithubAuth initializes a new GithubAuth with token
func NewGithubAuth(token, mountPath string) *GithubAuth {
func NewGithubAuth(token, mountPath string, namespace string) *GithubAuth {
githubAuth := &GithubAuth{
AccessToken: token,
MountPath: githubMountPath,
Namespace: namespace,
}
if mountPath != "" {
githubAuth.MountPath = mountPath
Expand All @@ -32,7 +34,7 @@ func NewGithubAuth(token, mountPath string) *GithubAuth {

// Authenticate authenticates with Vault and returns a token
func (g *GithubAuth) Authenticate(vaultClient *api.Client) error {
err := utils.LoginWithCachedToken(vaultClient, "github")
err := utils.LoginWithCachedToken(vaultClient, fmt.Sprintf("github_%s", g.Namespace))
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot retrieve cached token: %v. Generating a new one", err)
} else {
Expand All @@ -52,7 +54,7 @@ func (g *GithubAuth) Authenticate(vaultClient *api.Client) error {
utils.VerboseToStdErr("Hashicorp Vault authentication response: %v", data)

// If we cannot write the Vault token, we'll just have to login next time. Nothing showstopping.
err = utils.SetToken(vaultClient, "github", data.Auth.ClientToken)
err = utils.SetToken(vaultClient, fmt.Sprintf("github_%s", g.Namespace), data.Auth.ClientToken)
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot cache token for future runs: %v", err)
}
Expand Down
22 changes: 19 additions & 3 deletions pkg/auth/vault/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ func TestGithubLogin(t *testing.T) {
cluster := helpers.CreateTestAuthVault(t)
defer cluster.Cleanup()

github := vault.NewGithubAuth("123", "")
github := vault.NewGithubAuth("123", "", "default")

err := github.Authenticate(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken("github")
cachedToken, err := utils.ReadExistingToken("github_default")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -31,12 +31,28 @@ func TestGithubLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken("github")
newCachedToken, err := utils.ReadExistingToken("github_default")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}

if bytes.Compare(cachedToken, newCachedToken) != 0 {
t.Fatalf("expected same token %s but got %s", cachedToken, newCachedToken)
}

// We create a new connection to a specific namespace and create a different cache
namespaceCluster := helpers.CreateTestAuthVault(t)
defer namespaceCluster.Cleanup()

namespaceGithub := vault.NewGithubAuth("123", "", "my-other-namespace")

err = namespaceGithub.Authenticate(namespaceCluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

_, err = utils.ReadExistingToken("github_my-other-namespace")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
}
17 changes: 9 additions & 8 deletions pkg/auth/vault/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,30 @@ const (

// K8sAuth TODO
type K8sAuth struct {
// Optional, will use default path of auth/kubernetes if left blank
MountPath string

Role string
// Optional, will use default service account if left blank
TokenPath string

Role string
// Optional, will use default path of auth/kubernetes if left blank
MountPath string
// Optional, will use "default" if not provided
Namespace string
}

// NewK8sAuth initializes and returns a K8sAuth Struct
func NewK8sAuth(role, mountPath, tokenPath string) *K8sAuth {
func NewK8sAuth(role, mountPath, tokenPath string, namespace string) *K8sAuth {
k8sAuth := &K8sAuth{
Role: role,
MountPath: mountPath,
TokenPath: tokenPath,
Namespace: namespace,
}

return k8sAuth
}

// Authenticate authenticates with Vault via K8s and returns a token
func (k *K8sAuth) Authenticate(vaultClient *api.Client) error {
err := utils.LoginWithCachedToken(vaultClient, "kubernetes")
err := utils.LoginWithCachedToken(vaultClient, fmt.Sprintf("kubernetes_%s", k.Namespace))
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot retrieve cached token: %v. Generating a new one", err)
} else {
Expand Down Expand Up @@ -70,7 +71,7 @@ func (k *K8sAuth) Authenticate(vaultClient *api.Client) error {
utils.VerboseToStdErr("Hashicorp Vault authentication response: %v", data)

// If we cannot write the Vault token, we'll just have to login next time. Nothing showstopping.
err = utils.SetToken(vaultClient, "kubernetes", data.Auth.ClientToken)
err = utils.SetToken(vaultClient, fmt.Sprintf("kubernetes_%s", k.Namespace), data.Auth.ClientToken)
if err != nil {
utils.VerboseToStdErr("Hashicorp Vault cannot cache token for future runs: %v", err)
}
Expand Down
22 changes: 19 additions & 3 deletions pkg/auth/vault/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ func TestKubernetesAuth(t *testing.T) {
t.Fatalf("error writing token: %s", err)
}

k8s := vault.NewK8sAuth("role", "", string(filepath.Join(saPath, "token")))
k8s := vault.NewK8sAuth("role", "", string(filepath.Join(saPath, "token")), "default")

err = k8s.Authenticate(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken("kubernetes")
cachedToken, err := utils.ReadExistingToken("kubernetes_default")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -63,7 +63,7 @@ func TestKubernetesAuth(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken("kubernetes")
newCachedToken, err := utils.ReadExistingToken("kubernetes_default")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -72,6 +72,22 @@ func TestKubernetesAuth(t *testing.T) {
t.Fatalf("expected same token %s but got %s", cachedToken, newCachedToken)
}

// We create a new connection to a specific namespace and create a different cache
namespaceCluster := helpers.CreateTestAuthVault(t)
defer namespaceCluster.Cleanup()

namespaceK8s := vault.NewK8sAuth("role", "", string(filepath.Join(saPath, "token")), "my-other-namespace")

err = namespaceK8s.Authenticate(namespaceCluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected no errors but got: %s", err)
}

_, err = utils.ReadExistingToken("kubernetes_my-other-namespace")
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}

err = removeK8sToken()
if err != nil {
fmt.Println(err)
Expand Down
Loading
Loading