Skip to content

Commit

Permalink
chore: added Google Workload Identity Federation test
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreBeucher committed Dec 7, 2024
1 parent 566fc7f commit dd6420e
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 4 deletions.
10 changes: 10 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ tasks:
cmds:
- task: test-setup
- task: test-integ-run
- task: test-authentication
- task: test-teardown

test-setup:
Expand Down Expand Up @@ -182,6 +183,15 @@ tasks:
cmds:
- tests/install/test-install.sh

# Tests Novops authentication methods
test-authentication:
cmds:
- task: test-authentication-gcp-wif

# Test Google Worklow Identity Federation authentication
test-authentication-gcp-wif:
cmd: tests/authentication/test-gcp-workload-identity-federation-creds.sh

#
# Release
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash

# Test novops Google Workload Identity Federation authentication using Azure credentials
# - Run authentication steps in container to avoid side-effect with locally configured accounts
# - Generate an Azure token with Google credential file which Novops can read
# - Run Novops and check authentication happens properly

set -e

AZ_STACK_OUTPUTS=$(pulumi -C tests/setup/pulumi/azure/ -s test stack output --json --show-secrets)
AZ_TENANT=$(echo $AZ_STACK_OUTPUTS | jq -r .tenantId)
AZ_USERNAME=$(echo $AZ_STACK_OUTPUTS | jq -r .servicePrincipalClientId)
AZ_PASSWORD=$(echo $AZ_STACK_OUTPUTS | jq -r .password)
AZ_IDENTIFIER_URI=$(echo $AZ_STACK_OUTPUTS | jq -r .identifierUri)

echo "Login to Azure using Tenant ID '$AZ_TENANT' Service Principal '$AZ_USERNAME' to get token for Identifier URI '$AZ_IDENTIFIER_URI'"

mkdir -p ./tmp

# Authenticate with Azure in a container (to avoid side effect with local Azure config)
# And save token to file via bind mount
podman run -u 0 --rm -it -v $PWD:/novops --entrypoint bash bitnami/azure-cli:2.67.0 -c \
"az login --service-principal --username $AZ_USERNAME --password $AZ_PASSWORD --tenant $AZ_TENANT && \
az account get-access-token --resource $AZ_IDENTIFIER_URI --query accessToken --output tsv > /novops/tmp/az-token.txt"

# Generate Google credential JSON file for our Azure token
GCP_STACK_OUTPUTS=$(pulumi -C tests/setup/pulumi/gcp/ -s test stack output --json)
WIF_NAME=$(echo $GCP_STACK_OUTPUTS | jq -r .workloadIdentityPoolProviderName)
GCP_PROJECT_NAME=$(echo $GCP_STACK_OUTPUTS | jq -r .projectName)

echo "Using Workload Identity Federation provider: $WIF_NAME"

# Project name is asked but actually not used
gcloud iam workload-identity-pools create-cred-config \
--project "dummy" \
$WIF_NAME \
--credential-source-file=$PWD/tmp/az-token.txt \
--output-file=$PWD/tmp/gcp-auth.json

export GOOGLE_APPLICATION_CREDENTIALS="$PWD/tmp/gcp-auth.json"

echo "Google auth file ready in $GOOGLE_APPLICATION_CREDENTIALS"

RUST_LOG=novops=debug cargo run -- load -c tests/.novops.gcloud_secretmanager.yml --skip-tty-check
42 changes: 40 additions & 2 deletions tests/setup/pulumi/azure/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as az from "@pulumi/azure-native";
import * as az from "@pulumi/azure-native"
import * as azuread from "@pulumi/azuread"
import * as pulumi from "@pulumi/pulumi"

const resourceGroup = new az.resources.ResourceGroup("novops-testing", {
resourceGroupName: "novops-testing",
})

const currentConfig = az.authorization.getClientConfigOutput()

// Key Vault secrets from testing
const keyVault = new az.keyvault.Vault("novops-test", {
resourceGroupName: resourceGroup.name,
vaultName: "novops-test",
Expand Down Expand Up @@ -35,4 +38,39 @@ const secret = new az.keyvault.Secret("novops-test-secret", {
properties: {
value: "v3rySecret!",
},
})
})

// Entra ID app to test Google Workload Identity Federation auth

const appDisplayName = "novops-test-google-workload-id-fed"
export const identifierUri = "api://novops-test/google-workload-id-fed"

const app = new azuread.Application("novops-test-google-workload-id-fed-app", {
displayName: appDisplayName,
owners: [currentConfig.objectId],
identifierUris: [identifierUri]
})

const servicePrincipal = new azuread.ServicePrincipal("novops-test-google-workload-id-fed-service-principal", {
clientId: app.clientId,
owners: [currentConfig.objectId]
})
const servicePrincipalPassword = new azuread.ServicePrincipalPassword("novops-test-google-workload-id-fed-sp-password", {
servicePrincipalId: servicePrincipal.id,
endDate: "2099-01-01T01:01:42Z"
})

const roleReader = "acdd72a7-3385-48ef-bd42-f606fba81ae7"
const readerRoleAssignment = new az.authorization.RoleAssignment("novops-test-google-workload-id-fed-role-assignment", {
principalId: servicePrincipal.objectId,
principalType: "ServicePrincipal",
roleDefinitionId: pulumi.interpolate`/subscriptions/${currentConfig.subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${roleReader}`,
scope: pulumi.interpolate`/subscriptions/${currentConfig.subscriptionId}`,
})

export const servicePrincipalClientId = servicePrincipal.clientId
export const servicePrincipalObjectId = servicePrincipal.objectId
export const tenantId = servicePrincipal.applicationTenantId
export const applicationObjectId = app.objectId
export const applicationClientId = app.clientId
export const password = servicePrincipalPassword.value
2 changes: 1 addition & 1 deletion tests/setup/pulumi/gcp/Pulumi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ name: novops-test-infra-gcp
runtime: nodejs
description: Setup Google Cloud infra for Novops tests
config:
gcp:project: novops-testing
gcp:project: "398497848942"
93 changes: 92 additions & 1 deletion tests/setup/pulumi/gcp/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import * as gcp from "@pulumi/gcp";
import * as pulumi from "@pulumi/pulumi";

const gcpConfig = new pulumi.Config("gcp");
const projectId = gcpConfig.require("project")

const secret = new gcp.secretmanager.Secret("test-secret", {
secretId: "novops-test-secret",
Expand All @@ -10,4 +14,91 @@ const secret = new gcp.secretmanager.Secret("test-secret", {
const secretVersion = new gcp.secretmanager.SecretVersion("test-secret-version", {
secret: secret.id,
secretData: "very!S3cret",
})
})

// Workload Identity Federation config to allow Azure authentication and test of Google WIF
// Retrieve outputs from Azure setup stack
// As documented on https://cloud.google.com/iam/docs/workload-identity-federation-with-other-clouds#azure_2

const azStackRef = new pulumi.StackReference("az-stack-ref", { name: "pierrebeucher/novops-test-infra-azure/test" })
const azServicePrincipalObjectId = azStackRef.requireOutput("servicePrincipalObjectId") as pulumi.Output<string>
const azTenantId = azStackRef.requireOutput("tenantId") as pulumi.Output<string>
const azAppIdentifierUri = azStackRef.requireOutput("identifierUri") as pulumi.Output<string>

const poolId = "novops-test-identity-pool"
const providerId = "azure"

// Use an import-if-exists and retain pattern to avoid deletion on stack down and re-import on stack up
// Otherwise, as resources are soft-deleted, Google API would consider them already existing if Pulumi tried to recreate them
// See https://github.com/pulumi/pulumi-gcp/issues/1149
const identityPool = pulumi.output(gcp.iam.getWorkloadIdentityPool({
workloadIdentityPoolId: poolId,
}).then(existingPool =>
new gcp.iam.WorkloadIdentityPool("workload-identity-pool", {
workloadIdentityPoolId: poolId,
}, {
import: existingPool.workloadIdentityPoolId,
retainOnDelete: true,
})
).catch(e =>
new gcp.iam.WorkloadIdentityPool("workload-identity-pool", {
workloadIdentityPoolId: poolId,
}, {
retainOnDelete: true,
})
))

const workloadIdentityProvider = pulumi.output(gcp.iam.getWorkloadIdentityPoolProvider({
workloadIdentityPoolProviderId: providerId,
workloadIdentityPoolId: poolId
}).then(existingWifProvider =>
new gcp.iam.WorkloadIdentityPoolProvider("workload-identity-pool-provider", {
workloadIdentityPoolProviderId: providerId,
workloadIdentityPoolId: poolId,
oidc: {
issuerUri: pulumi.interpolate`https://sts.windows.net/${azTenantId}/`,
allowedAudiences: [azAppIdentifierUri],
},
attributeMapping: {
"google.subject": "assertion.sub",
},
}, {
import: existingWifProvider.name,
retainOnDelete: true,
})
).catch(e =>
new gcp.iam.WorkloadIdentityPoolProvider("workload-identity-pool-provider", {
workloadIdentityPoolProviderId: providerId,
workloadIdentityPoolId: poolId,
oidc: {
issuerUri: pulumi.interpolate`https://sts.windows.net/${azTenantId}/`,
allowedAudiences: [azAppIdentifierUri],
},
attributeMapping: {
"google.subject": "assertion.sub",
},
}, {
retainOnDelete: true,
})
))

const secretManagerIamBinding = new gcp.projects.IAMMember("secret-manager-iam-binding", {
project: projectId,
role: "roles/secretmanager.secretAccessor",
// Service Principal Object ID is the ID Google uses to identify or Azure user
member: pulumi.interpolate`principal://iam.googleapis.com/projects/${projectId}/locations/global/workloadIdentityPools/${poolId}/subject/${azServicePrincipalObjectId}`,
}, {
dependsOn: [workloadIdentityProvider]
})

const viewerIamBinding = new gcp.projects.IAMMember("viewer-iam-binding", {
project: projectId,
role: "roles/viewer",
member: pulumi.interpolate`principal://iam.googleapis.com/projects/${projectId}/locations/global/workloadIdentityPools/${poolId}/subject/${azServicePrincipalObjectId}`,
}, {
dependsOn: [workloadIdentityProvider]
})

export const workloadIdentityPoolName = identityPool.name
export const workloadIdentityPoolProviderName = workloadIdentityProvider.name
export const providerResourceName = workloadIdentityProvider.id
1 change: 1 addition & 0 deletions tests/setup/pulumi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@pulumi/aws": "^6.0.0",
"@pulumi/awsx": "^2.0.2",
"@pulumi/azure-native": "^2.48.0",
"@pulumi/azuread": "^6.0.1",
"@pulumi/docker": "^4.5.4",
"@pulumi/gcp": "^7.29.0",
"@pulumi/kubernetes": "^4.14.0",
Expand Down
Loading

0 comments on commit dd6420e

Please sign in to comment.