diff --git a/README.md b/README.md index 5ef07bc..6763a1b 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ To deploy it just run `kubectl apply -f secretdefinition-sample.yaml` | `azure-kv.tenant-id` | `""` | Azure KeyVault Tenant ID. `AZURE_TENANT_ID` environment would take precedence | | `azure-kv.client-id` | `""` | Azure KeyVault Cliend ID used to authenticate. `AZURE_CLIENT_ID` environment would take precedence | | `azure-kv.client-secret` | `""` | Azure KeyVault Client Secret used to authenticate. `AZURE_CLIENT_SECRET` environment would take precedence | -| `azure-kv.managed-client-id` | `""` | Azure Managed Identity Resource ID used to authenticate. `AZURE_MANAGED_CLIENT_ID` environment would take precedence | +| `azure-kv.managed-client-id` | `""` | Azure Managed Identity Client ID used to authenticate. `AZURE_MANAGED_CLIENT_ID` environment would take precedence | | `azure-kv.managed-resource-id` | `""` | Azure Managed Identity Resource ID used to authenticate. `AZURE_MANAGED_RESOURCE_ID` environment would take precedence | | `vault.url` | https://127.0.0.1:8200 | Vault address. `VAULT_ADDR` environment would take precedence. | | `vault.role-id` | `""` | Vault appRole `role_id`. `VAULT_ROLE_ID` environment would take precedence. | diff --git a/backend/azure_kv.go b/backend/azure_kv.go index 95c3984..836e9c6 100644 --- a/backend/azure_kv.go +++ b/backend/azure_kv.go @@ -8,7 +8,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" - "github.com/Azure/go-autorest/autorest" "github.com/go-logr/logr" "github.com/tuenti/secrets-manager/errors" ) @@ -69,6 +68,7 @@ func azureKeyVaultClient(ctx context.Context, l logr.Logger, cfg Config) (*azure if err != nil { logger.Error(err, "Error occured while creating Azure KV client") akvMetrics.updateLoginErrorsTotalMetric() + return nil, err } logger.Info("successfully logged into Azure KeyVault") @@ -91,11 +91,12 @@ func (c *azureKVClient) ReadSecret(path string, key string) (string, error) { if err != nil { errorType := errors.UnknownErrorType - if v, ok := err.(autorest.DetailedError); ok { - if v.StatusCode == 404 { + var responseError *azcore.ResponseError + if goerrors.As(err, &responseError) { + if responseError.StatusCode == 404 { errorType = errors.BackendSecretNotFoundErrorType } - if v.StatusCode == 403 { + if responseError.StatusCode == 403 { errorType = errors.BackendSecretForbiddenErrorType } } diff --git a/backend/azure_kv_metrics_test.go b/backend/azure_kv_metrics_test.go index 5c44216..3541fee 100644 --- a/backend/azure_kv_metrics_test.go +++ b/backend/azure_kv_metrics_test.go @@ -8,11 +8,6 @@ import ( "github.com/tuenti/secrets-manager/errors" ) -const ( - fakeKeyVaultName = "azure-keyvault-fake-name" - fakeKeyVaultTenant = "01234567-0123-0123-0123-0123456789ab" -) - func TestAzureKVUpdateLoginErrorsTotal(t *testing.T) { metrics := newAzureKVMetrics(fakeKeyVaultName, fakeKeyVaultTenant) azureKVLoginErrorsTotal.Reset() diff --git a/backend/azure_kv_test.go b/backend/azure_kv_test.go index deb5de9..2e012e1 100644 --- a/backend/azure_kv_test.go +++ b/backend/azure_kv_test.go @@ -2,13 +2,108 @@ package backend import ( "context" + "encoding/json" + "fmt" + "net/http" "testing" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" ) +const ( + fakeKeyVaultName = "azure-keyvault-fake-name" + fakeKeyVaultTenant = "01234567-0123-0123-0123-0123456789ab" + fakeKeyVaultSecret = "fake-secret" +) + +var akvSecrets = map[string]struct { + value string + access bool +}{ + fakeKeyVaultSecret: {value: "some-fake-value", access: true}, + "exists": {value: "yes", access: true}, + "internal-error": {value: "\"bad-scaped", access: true}, + "forbidden": {value: "yes", access: false}, +} + +func akvGetSecret(w http.ResponseWriter, r *http.Request) { + // Info about what keyvault responses should look like extracted from + // https://github.com/Azure/azure-sdk-for-go/blob/c73b114ded83c0a9c2685336b8b90836c1530cb3/sdk/keyvault/azsecrets/testdata/recordings/TestSetGetSecret.json + vars := mux.Vars(r) + jsonData := "{}" + if v, ok := akvSecrets[vars["secretName"]]; ok { + if v.access { + jsonData = fmt.Sprintf(` + { + "value": "%s", + "id": "https://%s.vault.azure.net/secrets/%s/3f3b11064811494a8a8b27edf4f0985b", + "attributes": { + "enabled": true, + "created": 1643130727, + "updated": 1643130727, + "recoveryLevel": "CustomizedRecoverable\u002BPurgeable", + "recoverableDays": 7 + } + }`, v.value, fakeKeyVaultName, vars["secretName"]) + } else { + w.WriteHeader(http.StatusForbidden) + } + } else { + jsonData = fmt.Sprintf(` + "error": { + "code": "SecretNotFound", + "message": "Secret not found: %s" + } + `, vars["secretName"]) + w.WriteHeader(http.StatusNotFound) + } + var response interface{} + + if err := json.Unmarshal([]byte(jsonData), &response); err != nil { + fmt.Printf("unable to unmarshal json %v", err) + w.WriteHeader(http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "application/json") + w.Header().Set("x-ms-keyvault-network-info", "conn_type=Ipv4;addr=72.49.29.93;act_addr_fam=InterNetwork;") + w.Header().Set("x-ms-keyvault-region", "westus2") + w.Header().Set("x-ms-keyvault-service-version", "1.9.264.2") + w.Header().Set("x-ms-request-id", "868ba1d2-efe7-4930-b3ad-d010cf499778") + w.Header().Set("X-Powered-By", "ASP.NET") + // Trick Azure client to make it think everything is legit + w.Header().Set( + "WWW-Authenticate", + "Bearer authorization=\u0022https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47\u0022, resource=\u0022https://vault.azure.net\u0022", + ) + json.NewEncoder(w).Encode(response) +} + +// Copied from https://github.com/Azure/azure-sdk-for-go/blob/35fb64f82ef3b3308f55b1da37c1fec36bdd4166/sdk/keyvault/azsecrets/utils_test.go +type FakeCredential struct { + accountName string + accountKey string +} + +func NewFakeCredential(accountName, accountKey string) *FakeCredential { + return &FakeCredential{ + accountName: accountName, + accountKey: accountKey, + } +} + +func (f *FakeCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (*azcore.AccessToken, error) { + return &azcore.AccessToken{ + Token: "faketoken", + ExpiresOn: time.Date(2040, time.January, 1, 1, 1, 1, 1, time.UTC), + }, nil +} + func TestGetAzureCredential(t *testing.T) { cases := []struct { cfg Config @@ -106,5 +201,67 @@ func TestAzureKeyVaultClient(t *testing.T) { cfg := Config{} client, err := azureKeyVaultClient(context.TODO(), logger, cfg) assert.NotNilf(t, err, "Empty config should generate an error") - assert.Nil(t, client, "Empty configu should not generate any client") + assert.Nilf(t, client, "Empty config should not generate any client") + + // Managed Identity auth is performed at client call, so the client generated is "valid" + cfg = Config{ + AzureKVTenantID: fakeKeyVaultTenant, + AzureKVClientID: "fake-client-id", + } + client, err = azureKeyVaultClient(context.TODO(), logger, cfg) + assert.NotNilf(t, err, "Invalid Service Principal Authentication should generate an error") + assert.Nilf(t, client, "Invalid Service Principal Authentication should not generate any client") + + // Authentication is performed at client call, so the client generated is "valid" + // This happens for both service principal and managed identity + cfg = Config{ + AzureKVTenantID: fakeKeyVaultTenant, + AzureKVClientID: "fake-client-id", + AzureKVClientSecret: "fake-client-secret", + } + client, err = azureKeyVaultClient(context.TODO(), logger, cfg) + assert.Nilf(t, err, "Service Principal Authentication should not generate error") + assert.NotNilf(t, client, "Service Principal Authentication should generate a client") + + cfg = Config{AzureKVManagedClientID: "fake-client-id"} + client, err = azureKeyVaultClient(context.TODO(), logger, cfg) + assert.Nilf(t, err, "Managed Identity Authentication should not generate error") + assert.NotNilf(t, client, "Managed Identity Authentication should generate a client") +} + +func TestAzureKVClientReadSecret(t *testing.T) { + akvMetrics = newAzureKVMetrics(fakeKeyVaultName, fakeKeyVaultTenant) + azClient, _ := azsecrets.NewClient( + testingCfg.VaultURL, // Is a mock server, valid for both cases + NewFakeCredential("fake", "fake"), + nil, + ) + client := azureKVClient{ + client: azClient, + keyvaultName: "fakekvurl", + context: context.TODO(), + logger: logger, + } + + value, err := client.ReadSecret("exists", "") + assert.Nil(t, err) + assert.Equal(t, akvSecrets["exists"].value, value) + + value, err = client.ReadSecret(fakeKeyVaultSecret, "") + assert.Nil(t, err) + assert.Equal(t, akvSecrets[fakeKeyVaultSecret].value, value) + + value, err = client.ReadSecret("forbidden", "") + assert.NotNil(t, err) + assert.Equal(t, "", value) + assert.IsType(t, new(azcore.ResponseError), err) + + value, err = client.ReadSecret("not-found", "") + assert.NotNil(t, err) + assert.Equal(t, "", value) + assert.IsType(t, new(azcore.ResponseError), err) + + value, err = client.ReadSecret("internal-error", "") + assert.NotNil(t, err) + assert.Equal(t, "", value) } diff --git a/backend/backend_test.go b/backend/backend_test.go index 3b7fb0f..e568694 100644 --- a/backend/backend_test.go +++ b/backend/backend_test.go @@ -3,10 +3,23 @@ package backend import ( "context" "fmt" + "net/http/httptest" + "os" + "sync" "testing" + "github.com/go-logr/logr" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/tuenti/secrets-manager/errors" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + testingCfg Config + server *httptest.Server + mutex sync.Mutex + logger logr.Logger ) func TestNotImplementedBackend(t *testing.T) { @@ -17,3 +30,45 @@ func TestNotImplementedBackend(t *testing.T) { _, err := NewBackendClient(ctx, backend, nil, cfg) assert.EqualError(t, err, fmt.Sprintf("[%s] backend %s not supported", errors.BackendNotImplementedErrorType, backend)) } + +func TestMain(m *testing.M) { + r := mux.NewRouter() + v1SysHandler := r.PathPrefix(fmt.Sprintf("/%s/sys", vaultAPIVersion)).Subrouter() + v1AuthHandler := r.PathPrefix(fmt.Sprintf("/%s/auth", vaultAPIVersion)).Subrouter() + v1SecretHandler := r.PathPrefix(fmt.Sprintf("/%s/secret", vaultAPIVersion)).Subrouter() + akvSecretsHandler := r.PathPrefix("/secrets").Subrouter() + + v1SysHandler.HandleFunc("/health", v1SysHealth).Methods("GET") + v1AuthHandler.HandleFunc("/token/lookup-self", v1AuthTokenLookupSelf).Methods("GET") + v1AuthHandler.HandleFunc("/token/renew-self", v1AuthTokenRenewSelf).Methods("PUT") + v1AuthHandler.HandleFunc("/approle/login", v1AuthAppRoleLogin).Methods("PUT") + v1AuthHandler.HandleFunc("/kubernetes/login", v1AuthKubernetesLogin).Methods("PUT") + v1SecretHandler.HandleFunc("/data/test", v1SecretTestKv2).Methods("GET") + v1SecretHandler.HandleFunc("/test", v1SecretTestKv1).Methods("GET") + + akvSecretsHandler.PathPrefix("/{secretName}").HandlerFunc(akvGetSecret).Methods("GET") + + server = httptest.NewServer(r) + defer server.Close() + + testingCfg = Config{ + VaultURL: string(server.URL), + VaultRoleID: vaultFakeRoleID, + VaultSecretID: vaultFakeSecretID, + VaultTokenPollingPeriod: 1, + VaultEngine: "kv2", + VaultApprolePath: vaultAppRolePath, + } + + vaultTestCfg = &testConfig{ + tokenRenewable: defaultTokenRenewable, + tokenTTL: defaultTokenTTL, + tokenRevoked: defaultRevokedToken, + invalidRoleID: defaultInvalidAppRole, + invalidSecretID: defaultInvalidAppRole, + } + + logger = zap.New(zap.UseDevMode(true)) + + os.Exit(m.Run()) +} diff --git a/backend/vault_test.go b/backend/vault_test.go index f16c824..29d47e9 100644 --- a/backend/vault_test.go +++ b/backend/vault_test.go @@ -5,20 +5,14 @@ import ( "encoding/json" "fmt" "net/http" - "net/http/httptest" - "os" "strings" - "sync" "testing" "time" - "github.com/go-logr/logr" - "github.com/gorilla/mux" "github.com/hashicorp/vault/api" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" "github.com/tuenti/secrets-manager/errors" - "sigs.k8s.io/controller-runtime/pkg/log/zap" ) const ( @@ -49,11 +43,7 @@ type testConfig struct { } var ( - vaultCfg Config - server *httptest.Server - mutex sync.Mutex - testCfg *testConfig - logger logr.Logger + vaultTestCfg *testConfig ) func v1SysHealth(w http.ResponseWriter, r *http.Request) { @@ -83,7 +73,7 @@ func v1SysHealth(w http.ResponseWriter, r *http.Request) { func v1AuthTokenLookupSelf(w http.ResponseWriter, r *http.Request) { var response interface{} jsonData := "" - if !testCfg.tokenRevoked { + if !vaultTestCfg.tokenRevoked { jsonData = fmt.Sprintf(` { "request_id": "8d70f864-5f77-44fe-0940-df085376101f", @@ -113,7 +103,7 @@ func v1AuthTokenLookupSelf(w http.ResponseWriter, r *http.Request) { "wrap_info": null, "warnings": null, "auth": null - }`, testCfg.tokenRenewable, testCfg.tokenTTL) + }`, vaultTestCfg.tokenRenewable, vaultTestCfg.tokenTTL) } else { jsonData = `{"errors":["permission denied"]}` w.WriteHeader(http.StatusForbidden) @@ -130,7 +120,7 @@ func v1AuthTokenLookupSelf(w http.ResponseWriter, r *http.Request) { func v1AuthTokenRenewSelf(w http.ResponseWriter, r *http.Request) { var response interface{} jsonData := "" - if !testCfg.tokenRevoked { + if !vaultTestCfg.tokenRevoked { jsonData = fmt.Sprintf(` { "request_id": "d8ae3e67-91a0-2f7a-528b-522048f9dad3", @@ -170,7 +160,7 @@ func v1AuthTokenRenewSelf(w http.ResponseWriter, r *http.Request) { func v1AuthKubernetesLogin(w http.ResponseWriter, r *http.Request) { var response interface{} jsonData := "" - if !testCfg.invalidKubernetesRole { + if !vaultTestCfg.invalidKubernetesRole { jsonData = fmt.Sprintf(` { "auth": { @@ -203,7 +193,7 @@ func v1AuthKubernetesLogin(w http.ResponseWriter, r *http.Request) { func v1AuthAppRoleLogin(w http.ResponseWriter, r *http.Request) { var response interface{} jsonData := "" - if !testCfg.invalidRoleID && !testCfg.invalidSecretID { + if !vaultTestCfg.invalidRoleID && !vaultTestCfg.invalidSecretID { jsonData = fmt.Sprintf(` { "request_id": "ecc0025f-040a-3c28-164e-0651abd7f6ac", @@ -235,7 +225,7 @@ func v1AuthAppRoleLogin(w http.ResponseWriter, r *http.Request) { } }`, fakeToken) - } else if testCfg.invalidRoleID { + } else if vaultTestCfg.invalidRoleID { jsonData = `{"errors":["invalid role ID"]}` w.WriteHeader(http.StatusBadRequest) } else { @@ -306,7 +296,7 @@ func v1SecretTestKv1(w http.ResponseWriter, r *http.Request) { func TestVaultLoginKubernetes(t *testing.T) { httpClient := new(http.Client) - vclient, _ := api.NewClient(&api.Config{Address: vaultCfg.VaultURL, HttpClient: httpClient}) + vclient, _ := api.NewClient(&api.Config{Address: testingCfg.VaultURL, HttpClient: httpClient}) c := &client{ vclient: vclient, logical: vclient.Logical(), @@ -318,7 +308,7 @@ func TestVaultLoginKubernetes(t *testing.T) { assert.Nil(t, err) mutex.Lock() defer mutex.Unlock() - testCfg.invalidKubernetesRole = true + vaultTestCfg.invalidKubernetesRole = true err2 := c.vaultKubernetesLogin(strings.NewReader(fakeKubernetesSAToken)) assert.NotNil(t, err2) } @@ -336,7 +326,7 @@ func TestVaultBackendInvalidCfg(t *testing.T) { func TestVaultBackend(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - client, err := NewBackendClient(ctx, "vault", logger, vaultCfg) + client, err := NewBackendClient(ctx, "vault", logger, testingCfg) assert.Nil(t, err) assert.NotNil(t, client) } @@ -344,27 +334,27 @@ func TestVaultBackend(t *testing.T) { func TestVaultLoginInvalidRoleId(t *testing.T) { mutex.Lock() defer mutex.Unlock() - testCfg.invalidRoleID = true - client, err := vaultClient(logger, vaultCfg) + vaultTestCfg.invalidRoleID = true + client, err := vaultClient(logger, testingCfg) assert.Nil(t, client) assert.NotNil(t, err) - testCfg.invalidRoleID = defaultInvalidAppRole + vaultTestCfg.invalidRoleID = defaultInvalidAppRole } func TestVaultLoginInvalidSecretId(t *testing.T) { mutex.Lock() defer mutex.Unlock() - testCfg.invalidSecretID = true - client, err := vaultClient(logger, vaultCfg) + vaultTestCfg.invalidSecretID = true + client, err := vaultClient(logger, testingCfg) assert.Nil(t, client) assert.NotNil(t, err) - testCfg.invalidSecretID = defaultInvalidAppRole + vaultTestCfg.invalidSecretID = defaultInvalidAppRole } func TestVaultClient(t *testing.T) { maxTokenTTL.Reset() - client, err := vaultClient(logger, vaultCfg) - metricMaxTokenTTL, _ := maxTokenTTL.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) + client, err := vaultClient(logger, testingCfg) + metricMaxTokenTTL, _ := maxTokenTTL.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) assert.Nil(t, err) assert.NotNil(t, client) assert.Equal(t, float64(client.maxTokenTTL), testutil.ToFloat64(metricMaxTokenTTL)) @@ -378,31 +368,31 @@ func TestVaultClientInvalidCfg(t *testing.T) { } func TestGetToken(t *testing.T) { - client, err := vaultClient(logger, vaultCfg) + client, err := vaultClient(logger, testingCfg) token, err := client.getToken() assert.NotNil(t, token) assert.Nil(t, err) } func TestGetTokenTTL(t *testing.T) { - client, err := vaultClient(logger, vaultCfg) + client, err := vaultClient(logger, testingCfg) tokenTTL.Reset() token, err := client.getToken() ttl, err := client.getTokenTTL(token) - metricTokenTTL, _ := tokenTTL.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) + metricTokenTTL, _ := tokenTTL.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) - assert.Equal(t, float64(testCfg.tokenTTL), testutil.ToFloat64(metricTokenTTL)) - assert.Equal(t, int64(testCfg.tokenTTL), ttl) + assert.Equal(t, float64(vaultTestCfg.tokenTTL), testutil.ToFloat64(metricTokenTTL)) + assert.Equal(t, int64(vaultTestCfg.tokenTTL), ttl) assert.Nil(t, err) } func TestRenewToken(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.tokenRenewable = true - testCfg.tokenTTL = 600 + vaultTestCfg.tokenRenewable = true + vaultTestCfg.tokenTTL = 600 client.maxTokenTTL = 6000 token, err := client.getToken() @@ -412,28 +402,28 @@ func TestRenewToken(t *testing.T) { } func TestRenewTokenRevokedToken(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.tokenRenewable = true - testCfg.tokenTTL = 600 + vaultTestCfg.tokenRenewable = true + vaultTestCfg.tokenTTL = 600 client.maxTokenTTL = 6000 token, err := client.getToken() - testCfg.tokenRevoked = true + vaultTestCfg.tokenRevoked = true tokenRenewalErrorsTotal.Reset() err = client.renewToken(token) - metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultRenewSelfOperationName, errors.UnknownErrorType) + metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultRenewSelfOperationName, errors.UnknownErrorType) assert.NotNil(t, err) assert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal)) } func TestTokenNotRenewableError(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.tokenRenewable = false - testCfg.tokenTTL = 600 + vaultTestCfg.tokenRenewable = false + vaultTestCfg.tokenTTL = 600 client.maxTokenTTL = 6000 token, err := client.getToken() @@ -441,76 +431,76 @@ func TestTokenNotRenewableError(t *testing.T) { tokenRenewalErrorsTotal.Reset() err = client.renewToken(token) - metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType) + metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType) assert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal)) assert.EqualError(t, err, fmt.Sprintf("[%s] vault token not renewable", errors.VaultTokenNotRenewableErrorType)) } func TestRenewalLoopRevokedToken(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.tokenRevoked = true + vaultTestCfg.tokenRevoked = true tokenRenewalErrorsTotal.Reset() client.renewalLoop() - metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultLookupSelfOperationName, errors.UnknownErrorType) + metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultLookupSelfOperationName, errors.UnknownErrorType) assert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal)) } func TestRenewalLoopNotRenewableToken(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.tokenRenewable = false - testCfg.tokenRevoked = false - testCfg.tokenTTL = 600 + vaultTestCfg.tokenRenewable = false + vaultTestCfg.tokenRevoked = false + vaultTestCfg.tokenTTL = 600 client.maxTokenTTL = 6000 tokenRenewalErrorsTotal.Reset() client.renewalLoop() - metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType) + metricTokenRenewalErrorsTotal, _ := tokenRenewalErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, vaultIsRenewableOperationName, errors.VaultTokenNotRenewableErrorType) assert.Equal(t, 1.0, testutil.ToFloat64(metricTokenRenewalErrorsTotal)) } func TestRenewalLoopInvalidRoleId(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.invalidRoleID = true - testCfg.tokenRevoked = true + vaultTestCfg.invalidRoleID = true + vaultTestCfg.tokenRevoked = true tokenRenewalErrorsTotal.Reset() loginErrorsTotal.Reset() client.renewalLoop() - loginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) + loginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) assert.Equal(t, 1.0, testutil.ToFloat64(loginErrorsTotal)) - testCfg.invalidRoleID = defaultInvalidAppRole - testCfg.tokenRevoked = defaultRevokedToken + vaultTestCfg.invalidRoleID = defaultInvalidAppRole + vaultTestCfg.tokenRevoked = defaultRevokedToken } func TestRenewalLoopInvalidSecretId(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) mutex.Lock() defer mutex.Unlock() - testCfg.invalidSecretID = true - testCfg.tokenRevoked = true + vaultTestCfg.invalidSecretID = true + vaultTestCfg.tokenRevoked = true tokenRenewalErrorsTotal.Reset() loginErrorsTotal.Reset() client.renewalLoop() - loginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) + loginErrorsTotal, _ := loginErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName) assert.Equal(t, 1.0, testutil.ToFloat64(loginErrorsTotal)) - testCfg.invalidSecretID = defaultInvalidAppRole - testCfg.tokenRevoked = defaultRevokedToken + vaultTestCfg.invalidSecretID = defaultInvalidAppRole + vaultTestCfg.tokenRevoked = defaultRevokedToken } func TestReadSecretKv2(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) secretValue, err := client.ReadSecret("/secret/data/test", "foo") assert.Nil(t, err) assert.Equal(t, "bar", secretValue) @@ -519,60 +509,22 @@ func TestReadSecretKv2(t *testing.T) { func TestReadSecretKv1(t *testing.T) { mutex.Lock() defer mutex.Unlock() - vaultCfg.VaultEngine = "kv1" - client, _ := vaultClient(logger, vaultCfg) + testingCfg.VaultEngine = "kv1" + client, _ := vaultClient(logger, testingCfg) secretValue, err := client.ReadSecret("/secret/test", "foo") assert.Nil(t, err) assert.Equal(t, "bar", secretValue) } func TestSecretNotFound(t *testing.T) { - client, _ := vaultClient(logger, vaultCfg) + client, _ := vaultClient(logger, testingCfg) path := "/secret/data/test" key := "foo2" secretReadErrorsTotal.Reset() secretValue, err := client.ReadSecret(path, key) - metricSecretReadErrorsTotal, _ := secretReadErrorsTotal.GetMetricWithLabelValues(vaultCfg.VaultURL, vaultCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, path, key, errors.BackendSecretNotFoundErrorType) + metricSecretReadErrorsTotal, _ := secretReadErrorsTotal.GetMetricWithLabelValues(testingCfg.VaultURL, testingCfg.VaultEngine, vaultFakeVersion, vaultFakeClusterID, vaultFakeClusterName, path, key, errors.BackendSecretNotFoundErrorType) assert.Empty(t, secretValue) assert.EqualError(t, err, fmt.Sprintf("[%s] secret key %s not found at %s", errors.BackendSecretNotFoundErrorType, key, path)) assert.Equal(t, 1.0, testutil.ToFloat64(metricSecretReadErrorsTotal)) } -func TestMain(m *testing.M) { - r := mux.NewRouter() - v1SysHandler := r.PathPrefix(fmt.Sprintf("/%s/sys", vaultAPIVersion)).Subrouter() - v1AuthHandler := r.PathPrefix(fmt.Sprintf("/%s/auth", vaultAPIVersion)).Subrouter() - v1SecretHandler := r.PathPrefix(fmt.Sprintf("/%s/secret", vaultAPIVersion)).Subrouter() - - v1SysHandler.HandleFunc("/health", v1SysHealth).Methods("GET") - v1AuthHandler.HandleFunc("/token/lookup-self", v1AuthTokenLookupSelf).Methods("GET") - v1AuthHandler.HandleFunc("/token/renew-self", v1AuthTokenRenewSelf).Methods("PUT") - v1AuthHandler.HandleFunc("/approle/login", v1AuthAppRoleLogin).Methods("PUT") - v1AuthHandler.HandleFunc("/kubernetes/login", v1AuthKubernetesLogin).Methods("PUT") - v1SecretHandler.HandleFunc("/data/test", v1SecretTestKv2).Methods("GET") - v1SecretHandler.HandleFunc("/test", v1SecretTestKv1).Methods("GET") - - server = httptest.NewServer(r) - defer server.Close() - - vaultCfg = Config{ - VaultURL: string(server.URL), - VaultRoleID: vaultFakeRoleID, - VaultSecretID: vaultFakeSecretID, - VaultTokenPollingPeriod: 1, - VaultEngine: "kv2", - VaultApprolePath: vaultAppRolePath, - } - - testCfg = &testConfig{ - tokenRenewable: defaultTokenRenewable, - tokenTTL: defaultTokenTTL, - tokenRevoked: defaultRevokedToken, - invalidRoleID: defaultInvalidAppRole, - invalidSecretID: defaultInvalidAppRole, - } - - logger = zap.New(zap.UseDevMode(true)) - - os.Exit(m.Run()) -} diff --git a/go.mod b/go.mod index 36fdb95..3b831af 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.6.0 - github.com/Azure/go-autorest/autorest v0.11.1 github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/go-logr/logr v0.3.0 github.com/gorilla/mux v1.7.4 diff --git a/main.go b/main.go index 24d1fed..0f3208a 100644 --- a/main.go +++ b/main.go @@ -99,8 +99,8 @@ func main() { flag.StringVar(&backendCfg.AzureKVTenantID, "azure-kv.tenant-id", "", "Azure KeyVault Tenant ID. AZURE_TENANT_ID environment would take precedence") flag.StringVar(&backendCfg.AzureKVClientID, "azure-kv.client-id", "", "Azure KeyVault ClientID used to authenticate. AZURE_CLIENT_ID environment would take precedence") flag.StringVar(&backendCfg.AzureKVClientSecret, "azure-kv.client-secret", "", "Azure KeyVault Client Secret used to authenticate. AZURE_CLIENT_SECRET environment would take precedence") - flag.StringVar(&backendCfg.AzureKVManagedClientID, "azure-kv.managed-client-id", "", "Azure KeyVault Client ID used to authenticate using managed identity. AZURE_MANAGED_CLIENT_ID environment would take precedence") - flag.StringVar(&backendCfg.AzureKVManagedResourceID, "azure-kv.managed-resource-id", "", "Azure KeyVault Resource ID used to authenticate using managed identity. AZURE_MANAGED_RESOURCE_ID environment would take precedence") + flag.StringVar(&backendCfg.AzureKVManagedClientID, "azure-kv.managed-client-id", "", "Azure Managed Identity Client ID used to authenticate. AZURE_MANAGED_CLIENT_ID environment would take precedence") + flag.StringVar(&backendCfg.AzureKVManagedResourceID, "azure-kv.managed-resource-id", "", "Azure Managed Identity Resource ID used to authenticate. AZURE_MANAGED_RESOURCE_ID environment would take precedence") flag.StringVar(&watchNamespaces, "watch-namespaces", "", "Comma separated list of namespaces that secrets-manager will watch for SecretDefinitions. By default all namespaces are watched.") flag.StringVar(&excludeNamespaces, "exclude-namespaces", "", "Comma separated list of namespaces that secrets-manager will not watch for SecretDefinitions. By default all namespaces are watched.")