From 8997c9f6e470c2d20b6785069f2a4e0e47221440 Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Wed, 4 Aug 2021 16:08:47 -0700 Subject: [PATCH] feat: rename TOKEN_FILE_PATH to AZURE_FEDERATED_TOKEN_FILE Signed-off-by: Anish Ramasekar --- Makefile | 12 ++++++++- examples/msal-go/Dockerfile | 25 +++++++++++++++++++ examples/msal-go/main.go | 14 ++++++++--- examples/msal-go/token_credential.go | 4 +-- .../msal-net/akvdotnet/TokenCredential.cs | 4 +-- pkg/webhook/consts.go | 12 ++++----- pkg/webhook/webhook.go | 6 ++--- pkg/webhook/webhook_test.go | 24 +++++++++--------- scripts/ci-e2e.sh | 7 ++++-- test/e2e/e2e.go | 7 +++--- test/e2e/e2e_test.go | 1 + test/e2e/helpers.go | 13 +++++++++- test/e2e/token_exchange.go | 6 ++--- 13 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 examples/msal-go/Dockerfile diff --git a/Makefile b/Makefile index a25c0949a..4268805c8 100644 --- a/Makefile +++ b/Makefile @@ -214,6 +214,15 @@ $(HELM): chmod +x "$(TOOLS_BIN_DIR)/$(HELM_BIN)" "$(HELM)" rm -rf helm* $(OS)-$(GOARCH) +## -------------------------------------- +## E2E images +## -------------------------------------- +MSAL_GO_E2E_IMAGE := $(REGISTRY)/msal-go-e2e:$(IMAGE_VERSION) + +.PHONY: docker-build-e2e-msal-go +docker-build-e2e-msal-go: + docker buildx build --no-cache -t $(MSAL_GO_E2E_IMAGE) -f examples/msal-go/Dockerfile --platform="linux/amd64" --output=$(OUTPUT_TYPE) examples/msal-go + ## -------------------------------------- ## Testing ## -------------------------------------- @@ -246,7 +255,7 @@ GINKGO_ARGS ?= -focus="$(GINKGO_FOCUS)" -skip="$(GINKGO_SKIP)" -nodes=$(GINKGO_N # E2E configurations KUBECONFIG ?= $(HOME)/.kube/config -E2E_ARGS := -kubeconfig=$(KUBECONFIG) -report-dir=$(PWD)/_artifacts -e2e.arc-cluster=$(ARC_CLUSTER) +E2E_ARGS := -kubeconfig=$(KUBECONFIG) -report-dir=$(PWD)/_artifacts -e2e.arc-cluster=$(ARC_CLUSTER) -e2e.token-exchange-image=$(MSAL_GO_E2E_IMAGE) E2E_EXTRA_ARGS ?= .PHONY: test-e2e-run @@ -271,6 +280,7 @@ kind-create: $(KIND) $(KUBECTL) .PHONY: kind-load-image kind-load-image: $(KIND) load docker-image $(WEBHOOK_IMAGE) --name $(KIND_CLUSTER_NAME) + $(KIND) load docker-image $(MSAL_GO_E2E_IMAGE) --name $(KIND_CLUSTER_NAME) .PHONY: kind-delete kind-delete: $(KIND) diff --git a/examples/msal-go/Dockerfile b/examples/msal-go/Dockerfile new file mode 100644 index 000000000..c73e83914 --- /dev/null +++ b/examples/msal-go/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.16 as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY token_credential.go token_credential.go + +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o msalgo . + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/msalgo . +USER nonroot:nonroot + +ENTRYPOINT ["/msalgo"] diff --git a/examples/msal-go/main.go b/examples/msal-go/main.go index c8dbbfa8e..fba07032c 100644 --- a/examples/msal-go/main.go +++ b/examples/msal-go/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault" "github.com/Azure/go-autorest/autorest" @@ -20,9 +21,14 @@ func main() { kvClient := keyvault.New() kvClient.Authorizer = autorest.NewBearerAuthorizerCallback(nil, clientAssertionBearerAuthorizerCallback) - secretBundle, err := kvClient.GetSecret(context.Background(), keyvaultURL, secretName, "") - if err != nil { - klog.Fatalf("failed to get secret from keyvault, err: %+v", err) + for { + secretBundle, err := kvClient.GetSecret(context.Background(), keyvaultURL, secretName, "") + if err != nil { + klog.Fatalf("failed to get secret from keyvault, err: %+v", err) + } + klog.InfoS("successfully got secret", "secret", *secretBundle.Value) + + // wait for 60 seconds before polling again + time.Sleep(60 * time.Second) } - klog.InfoS("successfully got secret", "secret", *secretBundle.Value) } diff --git a/examples/msal-go/token_credential.go b/examples/msal-go/token_credential.go index acd2818f8..c61e2f501 100644 --- a/examples/msal-go/token_credential.go +++ b/examples/msal-go/token_credential.go @@ -26,9 +26,9 @@ func clientAssertionBearerAuthorizerCallback(tenantID, resource string) (*autore // AZURE_CLIENT_ID with the clientID set in the service account annotation // AZURE_TENANT_ID with the tenantID set in the service account annotation. If not defined, then // the tenantID provided via aad-pi-webhook-config for the webhook will be used. - // TOKEN_FILE_PATH is the service account token path + // AZURE_FEDERATED_TOKEN_FILE is the service account token path clientID := os.Getenv("AZURE_CLIENT_ID") - tokenFilePath := os.Getenv("TOKEN_FILE_PATH") + tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") // generate a token using the msal confidential client // this will always generate a new token request to AAD diff --git a/examples/msal-net/akvdotnet/TokenCredential.cs b/examples/msal-net/akvdotnet/TokenCredential.cs index acbc0bcef..68aded9df 100644 --- a/examples/msal-net/akvdotnet/TokenCredential.cs +++ b/examples/msal-net/akvdotnet/TokenCredential.cs @@ -17,9 +17,9 @@ public MyClientAssertionCredential() // AZURE_CLIENT_ID with the clientID set in the service account annotation // AZURE_TENANT_ID with the tenantID set in the service account annotation. If not defined, then // the tenantID provided via aad-pi-webhook-config for the webhook will be used. - // TOKEN_FILE_PATH is the service account token path + // AZURE_FEDERATED_TOKEN_FILE is the service account token path var clientID = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); - var tokenPath = Environment.GetEnvironmentVariable("TOKEN_FILE_PATH"); + var tokenPath = Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE"); var tenantID = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); _confidentialClientApp = ConfidentialClientApplicationBuilder.Create(clientID) diff --git a/pkg/webhook/consts.go b/pkg/webhook/consts.go index a683fb2dd..f80d5bdeb 100644 --- a/pkg/webhook/consts.go +++ b/pkg/webhook/consts.go @@ -24,12 +24,12 @@ const ( // Environment variables injected in the pod const ( - AzureClientIDEnvVar = "AZURE_CLIENT_ID" - AzureTenantIDEnvVar = "AZURE_TENANT_ID" - TokenFilePathEnvVar = "TOKEN_FILE_PATH" // #nosec - AzureAuthorityHostEnvVar = "AZURE_AUTHORITY_HOST" - TokenFilePathName = "azure-identity-token" - TokenFileMountPath = "/var/run/secrets/tokens" // #nosec + AzureClientIDEnvVar = "AZURE_CLIENT_ID" + AzureTenantIDEnvVar = "AZURE_TENANT_ID" + AzureFederatedTokenFileEnvVar = "AZURE_FEDERATED_TOKEN_FILE" // #nosec + AzureAuthorityHostEnvVar = "AZURE_AUTHORITY_HOST" + TokenFilePathName = "azure-identity-token" + TokenFileMountPath = "/var/run/secrets/tokens" // #nosec // DefaultAudience is the audience added to the service account token audience // This value is to be consistent with other token exchange flows in AAD and has // no impact on the actual token exchange flow. diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index 2bcf63b4e..1d066fde7 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -228,9 +228,9 @@ func addEnvironmentVariables(container corev1.Container, clientID, tenantID, azu if _, ok := m[AzureTenantIDEnvVar]; !ok { container.Env = append(container.Env, corev1.EnvVar{Name: AzureTenantIDEnvVar, Value: tenantID}) } - // add the token file path env var - if _, ok := m[TokenFilePathEnvVar]; !ok { - container.Env = append(container.Env, corev1.EnvVar{Name: TokenFilePathEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName)}) + // add the token file env var + if _, ok := m[AzureFederatedTokenFileEnvVar]; !ok { + container.Env = append(container.Env, corev1.EnvVar{Name: AzureFederatedTokenFileEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName)}) } // add the azure authority host env var if _, ok := m[AzureAuthorityHostEnvVar]; !ok { diff --git a/pkg/webhook/webhook_test.go b/pkg/webhook/webhook_test.go index e856ab468..924fea3db 100644 --- a/pkg/webhook/webhook_test.go +++ b/pkg/webhook/webhook_test.go @@ -509,15 +509,15 @@ func TestAddEnvironmentVariables(t *testing.T) { Value: "clientID", }, { - Name: "AZURE_TENANT_ID", + Name: AzureTenantIDEnvVar, Value: "tenantID", }, { - Name: "TOKEN_FILE_PATH", + Name: AzureFederatedTokenFileEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName), }, { - Name: "AZURE_AUTHORITY_HOST", + Name: AzureAuthorityHostEnvVar, Value: "https://login.microsoftonline.com/", }, }, @@ -534,15 +534,15 @@ func TestAddEnvironmentVariables(t *testing.T) { Value: "myClientID", }, { - Name: "AZURE_TENANT_ID", + Name: AzureTenantIDEnvVar, Value: "myTenantID", }, { - Name: "TOKEN_FILE_PATH", + Name: AzureFederatedTokenFileEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName), }, { - Name: "AZURE_AUTHORITY_HOST", + Name: AzureAuthorityHostEnvVar, Value: "https://login.microsoftonline.com/", }, }, @@ -556,15 +556,15 @@ func TestAddEnvironmentVariables(t *testing.T) { Value: "myClientID", }, { - Name: "AZURE_TENANT_ID", + Name: AzureTenantIDEnvVar, Value: "myTenantID", }, { - Name: "TOKEN_FILE_PATH", + Name: AzureFederatedTokenFileEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName), }, { - Name: "AZURE_AUTHORITY_HOST", + Name: AzureAuthorityHostEnvVar, Value: "https://login.microsoftonline.com/", }, }, @@ -595,15 +595,15 @@ func TestAddEnvironmentVariables(t *testing.T) { Value: "clientID", }, { - Name: "AZURE_TENANT_ID", + Name: AzureTenantIDEnvVar, Value: "tenantID", }, { - Name: "TOKEN_FILE_PATH", + Name: AzureFederatedTokenFileEnvVar, Value: filepath.Join(TokenFileMountPath, TokenFilePathName), }, { - Name: "AZURE_AUTHORITY_HOST", + Name: AzureAuthorityHostEnvVar, Value: "https://login.microsoftonline.com/", }, }, diff --git a/scripts/ci-e2e.sh b/scripts/ci-e2e.sh index 72a4b6a7f..30dcb3047 100755 --- a/scripts/ci-e2e.sh +++ b/scripts/ci-e2e.sh @@ -20,7 +20,7 @@ create_cluster() { download_service_account_keys # create a kind cluster, then build and load the webhook manager image to the kind cluster make kind-create - [[ "${SKIP_IMAGE_BUILD:-}" == "true" ]] || OUTPUT_TYPE="type=docker" make docker-build-webhook + [[ "${SKIP_IMAGE_BUILD:-}" == "true" ]] || OUTPUT_TYPE="type=docker" make docker-build-webhook docker-build-e2e-msal-go make kind-load-image else : "${REGISTRY:?Environment variable empty or not defined.}" @@ -106,7 +106,10 @@ test_helm_chart() { --namespace aad-pi-webhook-system \ --wait poll_webhook_readiness - make test-e2e-run + # TODO (aramase) remove token exchange from GINKGO_SKIP after v0.4.0 release is published + # Skipping TokenExchange test for the current release as we're using the latest msal-go image + # which is updated to use AZURE_FEDERATED_TOKEN_FILE for token path. + GINKGO_SKIP=TokenExchange make test-e2e-run ${HELM} upgrade --install pod-identity-webhook "${REPO_ROOT}/manifest_staging/charts/pod-identity-webhook" \ --set image.repository="${REGISTRY:-mcr.microsoft.com/oss/azure/aad-pod-managed-identity/webhook}" \ diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index d3a8e88b7..5131b42ee 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -25,9 +25,10 @@ import ( ) var ( - arcCluster bool - c *kubernetes.Clientset - coreNamespaces = []string{ + arcCluster bool + tokenExchangeE2EImage string + c *kubernetes.Clientset + coreNamespaces = []string{ metav1.NamespaceSystem, "aad-pi-webhook-system", } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 1243d2461..80f166e1e 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -13,6 +13,7 @@ import ( func init() { flag.BoolVar(&arcCluster, "e2e.arc-cluster", false, "Running on an arc-enabled cluster") + flag.StringVar(&tokenExchangeE2EImage, "e2e.token-exchange-image", "aramase/dotnet:v0.4", "The image to use for token exchange tests") } // handleFlags sets up all flags and parses the command line. diff --git a/test/e2e/helpers.go b/test/e2e/helpers.go index e4ade4f4d..a208c6993 100644 --- a/test/e2e/helpers.go +++ b/test/e2e/helpers.go @@ -216,7 +216,6 @@ func validateMutatedPod(f *framework.Framework, pod *corev1.Pod, skipContainers for _, injected := range []string{ webhook.AzureClientIDEnvVar, webhook.AzureTenantIDEnvVar, - webhook.TokenFilePathEnvVar, webhook.AzureAuthorityHostEnvVar, } { if _, ok := m[injected]; !ok { @@ -224,6 +223,18 @@ func validateMutatedPod(f *framework.Framework, pod *corev1.Pod, skipContainers } } + // v0.3.0 injects the token file path as TOKEN_FILE_PATH environment variable. For v0.3.0+ the environment variable + // is updated to AZURE_FEDERATED_TOKEN_FILE. Adding this check to support upgrade tests from v0.3.0 to v0.3.0+ + // TODO (aramase) remove this after v0.4.0 + framework.Logf("ensuring that the token file path environment variable is injected to %s in %s", container.Name, pod.Name) + tokenFilePathSet := false + if _, ok := m["TOKEN_FILE_PATH"]; ok { + tokenFilePathSet = true + } + if _, ok := m[webhook.AzureFederatedTokenFileEnvVar]; !ok && !tokenFilePathSet { + framework.Failf("container %s in pod %s does not have env var %s injected", container.Name, pod.Name, webhook.AzureFederatedTokenFileEnvVar) + } + framework.Logf("ensuring that azure-identity-token is mounted to %s", container.Name) found := false for _, volumeMount := range container.VolumeMounts { diff --git a/test/e2e/token_exchange.go b/test/e2e/token_exchange.go index 603fcaeb5..5b09e1876 100644 --- a/test/e2e/token_exchange.go +++ b/test/e2e/token_exchange.go @@ -21,7 +21,7 @@ import ( var _ = ginkgo.Describe("TokenExchange [KindOnly]", func() { f := framework.NewDefaultFramework("token-exchange") - // E2E scenario from https://github.com/Azure/aad-pod-managed-identity/tree/main/examples/msal-net/akvdotnet + // E2E scenario from https://github.com/Azure/aad-pod-managed-identity/tree/main/examples/msal-go ginkgo.It("should exchange the service account token for a valid AAD token", func() { clientID, ok := os.LookupEnv("APPLICATION_CLIENT_ID") gomega.Expect(ok).To(gomega.BeTrue(), "APPLICATION_CLIENT_ID must be set") @@ -39,7 +39,7 @@ var _ = ginkgo.Describe("TokenExchange [KindOnly]", func() { f.ClientSet, namespace, serviceAccount, - "aramase/dotnet:v0.4", + tokenExchangeE2EImage, nil, nil, []corev1.EnvVar{{ @@ -63,7 +63,7 @@ var _ = ginkgo.Describe("TokenExchange [KindOnly]", func() { return false } framework.Logf("stdout: %s", stdout) - return strings.Contains(stdout, "Your secret is Hello!") + return strings.Contains(stdout, `"successfully got secret" secret="Hello!"`) }, framework.PollShortTimeout, framework.Poll).Should(gomega.BeTrue()) } })