Skip to content

Commit

Permalink
feat: Enable caching tokens for multiple vault namespaces
Browse files Browse the repository at this point in the history
Signed-off-by: sbene <sebastien.bene@ubisoft.com>
  • Loading branch information
sbene committed Feb 14, 2025
1 parent b046a7d commit d2907ea
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 38 deletions.
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

0 comments on commit d2907ea

Please sign in to comment.