| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package keyvault
- import (
- "context"
- "net/http"
- "os"
- "strings"
- "testing"
- "github.com/Azure/go-autorest/autorest"
- "github.com/Azure/go-autorest/autorest/adal"
- tassert "github.com/stretchr/testify/assert"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/utils/pointer"
- "sigs.k8s.io/controller-runtime/pkg/client"
- clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
- v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
- utilfake "github.com/external-secrets/external-secrets/pkg/provider/util/fake"
- )
- var vaultURL = "https://local.vault.url"
- func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
- namespace := "internal"
- identityID := "1234"
- authType := esv1beta1.AzureManagedIdentity
- store := esv1beta1.SecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- },
- Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{AzureKV: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- IdentityID: &identityID,
- VaultURL: &vaultURL,
- }}},
- }
- k8sClient := clientfake.NewClientBuilder().Build()
- az := &Azure{
- crClient: k8sClient,
- namespace: namespace,
- provider: store.Spec.Provider.AzureKV,
- store: &store,
- }
- authorizer, err := az.authorizerForManagedIdentity()
- if err != nil {
- // On non Azure environment, MSI auth not available, so this error should be returned
- tassert.EqualError(t, err, "failed to get oauth token from MSI: MSI not available")
- } else {
- // On Azure (where GitHub Actions are running) a secretClient is returned, as only an Authorizer is configured, but no token is requested for MI
- tassert.NotNil(t, authorizer)
- }
- }
- func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
- const (
- tenantID = "my-tenant-id"
- clientID = "my-client-id"
- azAccessToken = "my-access-token"
- saToken = "FAKETOKEN"
- saName = "az-wi"
- namespace = "default"
- )
- // create a temporary file to imitate
- // azure workload identity webhook
- // see AZURE_FEDERATED_TOKEN_FILE
- tf, err := os.CreateTemp("", "")
- tassert.Nil(t, err)
- defer os.RemoveAll(tf.Name())
- _, err = tf.WriteString(saToken)
- tassert.Nil(t, err)
- tokenFile := tf.Name()
- authType := esv1beta1.AzureWorkloadIdentity
- defaultProvider := &esv1beta1.AzureKVProvider{
- VaultURL: &vaultURL,
- AuthType: &authType,
- ServiceAccountRef: &v1.ServiceAccountSelector{
- Name: saName,
- },
- }
- type testCase struct {
- name string
- provider *esv1beta1.AzureKVProvider
- k8sObjects []client.Object
- prep func(*testing.T)
- expErr string
- }
- for _, row := range []testCase{
- {
- name: "missing service account",
- provider: defaultProvider,
- expErr: "serviceaccounts \"" + saName + "\" not found",
- },
- {
- name: "missing webhook env vars",
- provider: &esv1beta1.AzureKVProvider{},
- expErr: "missing environment variables. AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE must be set",
- },
- {
- name: "missing workload identity token file",
- provider: &esv1beta1.AzureKVProvider{},
- prep: func(t *testing.T) {
- t.Setenv("AZURE_CLIENT_ID", clientID)
- t.Setenv("AZURE_TENANT_ID", tenantID)
- t.Setenv("AZURE_FEDERATED_TOKEN_FILE", "invalid file")
- },
- expErr: "unable to read token file invalid file: open invalid file: no such file or directory",
- },
- {
- name: "correct workload identity",
- provider: &esv1beta1.AzureKVProvider{},
- prep: func(t *testing.T) {
- t.Setenv("AZURE_CLIENT_ID", clientID)
- t.Setenv("AZURE_TENANT_ID", tenantID)
- t.Setenv("AZURE_FEDERATED_TOKEN_FILE", tokenFile)
- },
- },
- {
- name: "missing sa annotations",
- provider: defaultProvider,
- k8sObjects: []client.Object{
- &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: saName,
- Namespace: namespace,
- Annotations: map[string]string{},
- },
- },
- },
- expErr: "missing service account annotation: azure.workload.identity/client-id",
- },
- {
- name: "successful case",
- provider: defaultProvider,
- k8sObjects: []client.Object{
- &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: saName,
- Namespace: namespace,
- Annotations: map[string]string{
- AnnotationClientID: clientID,
- AnnotationTenantID: tenantID,
- },
- },
- },
- },
- },
- } {
- t.Run(row.name, func(t *testing.T) {
- store := esv1beta1.SecretStore{
- Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{
- AzureKV: row.provider,
- }},
- }
- k8sClient := clientfake.NewClientBuilder().
- WithObjects(row.k8sObjects...).
- Build()
- az := &Azure{
- store: &store,
- namespace: namespace,
- crClient: k8sClient,
- kubeClient: utilfake.NewCreateTokenMock().WithToken(saToken),
- provider: store.Spec.Provider.AzureKV,
- }
- tokenProvider := func(ctx context.Context, token, clientID, tenantID, aadEndpoint, kvResource string) (adal.OAuthTokenProvider, error) {
- tassert.Equal(t, token, saToken)
- tassert.Equal(t, clientID, clientID)
- tassert.Equal(t, tenantID, tenantID)
- return &tokenProvider{accessToken: azAccessToken}, nil
- }
- if row.prep != nil {
- row.prep(t)
- }
- authorizer, err := az.authorizerForWorkloadIdentity(context.Background(), tokenProvider)
- if row.expErr == "" {
- tassert.NotNil(t, authorizer)
- tassert.Equal(t, getTokenFromAuthorizer(t, authorizer), azAccessToken)
- } else {
- tassert.EqualError(t, err, row.expErr)
- }
- })
- }
- }
- func TestAuth(t *testing.T) {
- defaultStore := esv1beta1.SecretStore{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: "default",
- },
- Spec: esv1beta1.SecretStoreSpec{
- Provider: &esv1beta1.SecretStoreProvider{},
- },
- }
- authType := esv1beta1.AzureServicePrincipal
- type testCase struct {
- name string
- provider *esv1beta1.AzureKVProvider
- store esv1beta1.GenericStore
- objects []client.Object
- expErr string
- }
- for _, row := range []testCase{
- {
- name: "bad config",
- expErr: "missing secretRef in provider config",
- store: &defaultStore,
- provider: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- VaultURL: &vaultURL,
- TenantID: pointer.StringPtr("mytenant"),
- },
- },
- {
- name: "bad config",
- expErr: "missing accessKeyID/secretAccessKey in store config",
- store: &defaultStore,
- provider: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- VaultURL: &vaultURL,
- TenantID: pointer.StringPtr("mytenant"),
- AuthSecretRef: &esv1beta1.AzureKVAuth{},
- },
- },
- {
- name: "bad config: missing secret",
- expErr: "could not find secret default/password: secrets \"password\" not found",
- store: &defaultStore,
- provider: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- VaultURL: &vaultURL,
- TenantID: pointer.StringPtr("mytenant"),
- AuthSecretRef: &esv1beta1.AzureKVAuth{
- ClientSecret: &v1.SecretKeySelector{Name: "password"},
- ClientID: &v1.SecretKeySelector{Name: "password"},
- },
- },
- },
- {
- name: "cluster secret store",
- expErr: "could not find secret foo/password: secrets \"password\" not found",
- store: &esv1beta1.ClusterSecretStore{
- TypeMeta: metav1.TypeMeta{
- Kind: esv1beta1.ClusterSecretStoreKind,
- },
- Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{}},
- },
- provider: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- VaultURL: &vaultURL,
- TenantID: pointer.StringPtr("mytenant"),
- AuthSecretRef: &esv1beta1.AzureKVAuth{
- ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.StringPtr("foo")},
- ClientID: &v1.SecretKeySelector{Name: "password", Namespace: pointer.StringPtr("foo")},
- },
- },
- },
- {
- name: "correct cluster secret store",
- objects: []client.Object{&corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "password",
- Namespace: "foo",
- },
- Data: map[string][]byte{
- "id": []byte("foo"),
- "secret": []byte("bar"),
- },
- }},
- store: &esv1beta1.ClusterSecretStore{
- TypeMeta: metav1.TypeMeta{
- Kind: esv1beta1.ClusterSecretStoreKind,
- },
- Spec: esv1beta1.SecretStoreSpec{Provider: &esv1beta1.SecretStoreProvider{}},
- },
- provider: &esv1beta1.AzureKVProvider{
- AuthType: &authType,
- VaultURL: &vaultURL,
- TenantID: pointer.StringPtr("mytenant"),
- AuthSecretRef: &esv1beta1.AzureKVAuth{
- ClientSecret: &v1.SecretKeySelector{Name: "password", Namespace: pointer.StringPtr("foo"), Key: "secret"},
- ClientID: &v1.SecretKeySelector{Name: "password", Namespace: pointer.StringPtr("foo"), Key: "id"},
- },
- },
- },
- } {
- t.Run(row.name, func(t *testing.T) {
- k8sClient := clientfake.NewClientBuilder().WithObjects(row.objects...).Build()
- spec := row.store.GetSpec()
- spec.Provider.AzureKV = row.provider
- az := &Azure{
- crClient: k8sClient,
- namespace: "default",
- provider: spec.Provider.AzureKV,
- store: row.store,
- }
- authorizer, err := az.authorizerForServicePrincipal(context.Background())
- if row.expErr == "" {
- tassert.Nil(t, err)
- tassert.NotNil(t, authorizer)
- } else {
- tassert.EqualError(t, err, row.expErr)
- }
- })
- }
- }
- func getTokenFromAuthorizer(t *testing.T, authorizer autorest.Authorizer) string {
- rq, _ := http.NewRequest("POST", "http://example.com", http.NoBody)
- _, err := authorizer.WithAuthorization()(
- autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
- return rq, nil
- })).Prepare(rq)
- tassert.Nil(t, err)
- return strings.TrimPrefix(rq.Header.Get("Authorization"), "Bearer ")
- }
|