//go:build integration // +build integration // can be execute with go test -tags=integration ./integration/... package main import ( "context" "encoding/json" "fmt" "testing" "time" "github.com/SAP/jenkins-library/pkg/vault" "github.com/hashicorp/vault/api" "github.com/stretchr/testify/assert" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) type SecretData = map[string]interface{} func TestGetVaultSecret(t *testing.T) { t.Parallel() ctx := context.Background() const testToken = "vault-token" req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ AlwaysPullImage: true, Image: "vault:1.4.3", ExposedPorts: []string{"8200/tcp"}, Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken}, WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)}, Started: true, } vaultContainer, err := testcontainers.GenericContainer(ctx, req) assert.NoError(t, err) defer vaultContainer.Terminate(ctx) ip, err := vaultContainer.Host(ctx) assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) config := &vault.Config{Config: &api.Config{Address: host}} // setup vault for testing secretData := SecretData{ "key1": "value1", "key2": "value2", } setupVault(t, config, testToken, secretData) client, err := vault.NewClient(config, testToken) assert.NoError(t, err) secret, err := client.GetKvSecret("secret/test") assert.NoError(t, err) assert.Equal(t, "value1", secret["key1"]) assert.Equal(t, "value2", secret["key2"]) secret, err = client.GetKvSecret("kv/test") assert.NoError(t, err) assert.Equal(t, "value1", secret["key1"]) assert.Equal(t, "value2", secret["key2"]) } func TestWriteVaultSecret(t *testing.T) { t.Parallel() ctx := context.Background() const testToken = "vault-token" req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ AlwaysPullImage: true, Image: "vault:1.4.3", ExposedPorts: []string{"8200/tcp"}, Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken}, WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)}, Started: true, } vaultContainer, err := testcontainers.GenericContainer(ctx, req) assert.NoError(t, err) defer vaultContainer.Terminate(ctx) ip, err := vaultContainer.Host(ctx) assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) config := &vault.Config{Config: &api.Config{Address: host}} // setup vault for testing secretData := map[string]string{ "key1": "value1", "key2": "value2", } client, err := vault.NewClient(config, testToken) assert.NoError(t, err) err = client.WriteKvSecret("secret/test", secretData) assert.NoError(t, err) secret, err := client.GetKvSecret("secret/test") assert.NoError(t, err) assert.Equal(t, "value1", secret["key1"]) assert.Equal(t, "value2", secret["key2"]) // enabling KV engine 1 vaultClient, err := api.NewClient(config.Config) assert.NoError(t, err) vaultClient.SetToken(testToken) _, err = vaultClient.Logical().Write("sys/mounts/kv", SecretData{ "path": "kv", "type": "kv", "options": SecretData{ "version": "1", }, }) assert.NoError(t, err) err = client.WriteKvSecret("secret/test1", secretData) assert.NoError(t, err) secret, err = client.GetKvSecret("secret/test1") assert.NoError(t, err) assert.Equal(t, "value1", secret["key1"]) assert.Equal(t, "value2", secret["key2"]) } func TestVaultAppRole(t *testing.T) { t.Parallel() ctx := context.Background() const testToken = "vault-token" const appRolePath = "auth/approle/role/test" const appRoleName = "test" req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ AlwaysPullImage: true, Image: "vault:1.4.3", ExposedPorts: []string{"8200/tcp"}, Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken}, WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)}, Started: true, } vaultContainer, err := testcontainers.GenericContainer(ctx, req) assert.NoError(t, err) defer vaultContainer.Terminate(ctx) ip, err := vaultContainer.Host(ctx) assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) config := &vault.Config{Config: &api.Config{Address: host}} secretIDMetadata := map[string]interface{}{ "field1": "value1", } roleID, secretID := setupVaultAppRole(t, config, testToken, appRolePath, secretIDMetadata) t.Run("Test Vault AppRole login", func(t *testing.T) { client, err := vault.NewClientWithAppRole(config, roleID, secretID) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) assert.Equal(t, meta["field1"], "value1") assert.Equal(t, meta["role_name"], "test") assert.NoError(t, err) }) t.Run("Test Vault AppRoleTTL Fetch", func(t *testing.T) { client, err := vault.NewClient(config, testToken) assert.NoError(t, err) ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName) assert.NoError(t, err) assert.Equal(t, time.Duration(90*24*time.Hour), ttl.Round(time.Hour)) }) t.Run("Test Vault AppRole Rotation", func(t *testing.T) { client, err := vault.NewClient(config, testToken) assert.NoError(t, err) newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName) assert.NoError(t, err) assert.NotEmpty(t, newSecretID) assert.NotEqual(t, secretID, newSecretID) // verify metadata is not broken client, err = vault.NewClientWithAppRole(config, roleID, newSecretID) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) assert.Equal(t, meta["field1"], "value1") assert.Equal(t, meta["role_name"], "test") assert.NoError(t, err) }) t.Run("Test Fetching RoleName from vault", func(t *testing.T) { client, err := vault.NewClientWithAppRole(config, roleID, secretID) assert.NoError(t, err) fetchedRoleName, err := client.GetAppRoleName() assert.NoError(t, err) assert.Equal(t, appRoleName, fetchedRoleName) }) } func TestTokenRevocation(t *testing.T) { t.Parallel() ctx := context.Background() const testToken = "vault-token" const appRolePath = "auth/approle/role/test" const appRoleName = "test" req := testcontainers.GenericContainerRequest{ ContainerRequest: testcontainers.ContainerRequest{ AlwaysPullImage: true, Image: "vault:1.4.3", ExposedPorts: []string{"8200/tcp"}, Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken}, WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)}, Started: true, } vaultContainer, err := testcontainers.GenericContainer(ctx, req) assert.NoError(t, err) defer vaultContainer.Terminate(ctx) ip, err := vaultContainer.Host(ctx) assert.NoError(t, err) port, err := vaultContainer.MappedPort(ctx, "8200") host := fmt.Sprintf("http://%s:%s", ip, port.Port()) config := &vault.Config{Config: &api.Config{Address: host}} secretIDMetadata := map[string]interface{}{ "field1": "value1", } roleID, secretID := setupVaultAppRole(t, config, testToken, appRolePath, secretIDMetadata) t.Run("Test Revocation works", func(t *testing.T) { client, err := vault.NewClientWithAppRole(config, roleID, secretID) assert.NoError(t, err) secret, err := client.GetSecret("auth/token/lookup-self") meta := secret.Data["meta"].(SecretData) assert.Equal(t, meta["field1"], "value1") assert.Equal(t, meta["role_name"], "test") assert.NoError(t, err) err = client.RevokeToken() assert.NoError(t, err) _, err = client.GetSecret("auth/token/lookup-self") assert.Error(t, err) }) } func setupVaultAppRole(t *testing.T, config *vault.Config, token, appRolePath string, metadata map[string]interface{}) (string, string) { t.Helper() client, err := api.NewClient(config.Config) assert.NoError(t, err) client.SetToken(token) lClient := client.Logical() _, err = lClient.Write("sys/auth/approle", SecretData{ "type": "approle", "config": map[string]interface{}{ "default_lease_ttl": "7776000s", "max_lease_ttl": "7776000s", }, }) assert.NoError(t, err) _, err = lClient.Write("auth/approle/role/test", SecretData{ "secret_id_ttl": 7776000, }) assert.NoError(t, err) metadataJson, err := json.Marshal(metadata) assert.NoError(t, err) res, err := lClient.Write("auth/approle/role/test/secret-id", SecretData{ "metadata": string(metadataJson), }) assert.NoError(t, err) secretID := res.Data["secret_id"] res, err = lClient.Read("auth/approle/role/test/role-id") assert.NoError(t, err) roleID := res.Data["role_id"] return roleID.(string), secretID.(string) } func setupVault(t *testing.T, config *vault.Config, token string, secret SecretData) { t.Helper() client, err := api.NewClient(config.Config) assert.NoError(t, err) client.SetToken(token) _, err = client.Logical().Write("secret/data/test", SecretData{"data": secret}) assert.NoError(t, err) // enabling KV engine 1 _, err = client.Logical().Write("sys/mounts/kv", SecretData{ "path": "kv", "type": "kv", "options": SecretData{ "version": "1", }, }) assert.NoError(t, err) _, err = client.Logical().Write("kv/test", secret) assert.NoError(t, err) }