diff --git a/azure-pipelines/scripts/seed-keyvault.sh b/azure-pipelines/scripts/seed-keyvault.sh index 3112d19..9432746 100644 --- a/azure-pipelines/scripts/seed-keyvault.sh +++ b/azure-pipelines/scripts/seed-keyvault.sh @@ -57,6 +57,31 @@ or_placeholder() { echo "Seeding secrets into Key Vault '$KEY_VAULT_NAME'..." +# The deployment step that runs just before this one (re)assigns this principal's +# 'Key Vault Secrets Officer' role. Newly created Key Vault role assignments can +# take several minutes to become effective on the *data plane*, so a write that +# happens too soon fails with ForbiddenByRbac. Probe with a throwaway secret +# until the data-plane access is live (or give up after ~10 min and surface the +# real error). +wait_for_keyvault_rbac() { + local attempt=1 max=30 + while ! az keyvault secret set --vault-name "$KEY_VAULT_NAME" \ + --name rbac-probe --value ok --output none 2>/dev/null; do + if [ "$attempt" -ge "$max" ]; then + echo "ERROR: Key Vault '$KEY_VAULT_NAME' data-plane RBAC not effective after $((max * 20))s." >&2 + # Final attempt without suppression so the real error reaches the log. + az keyvault secret set --vault-name "$KEY_VAULT_NAME" --name rbac-probe --value ok --output none + exit 1 + fi + echo " … waiting for Key Vault RBAC to propagate (attempt $attempt/$max)" + sleep 20 + attempt=$((attempt + 1)) + done + echo " ✔ Key Vault data-plane access confirmed" +} + +wait_for_keyvault_rbac + # --- Connection secrets fetched from the Azure control plane (work even when the # data plane is private) --- MONGO_URI=$(az cosmosdb keys list --name "$COSMOS_ACCOUNT" --resource-group "$RESOURCE_GROUP" \ diff --git a/infra/main.bicep b/infra/main.bicep index 901b337..dd046b2 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -426,6 +426,10 @@ resource kvPipelineRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = i principalId: pipelineSpOid principalType: 'ServicePrincipal' } + // keyVaultRes is an 'existing' reference, so Bicep infers no dependency on the + // module that creates the vault. Make it explicit so the assignment can't race + // ahead of vault creation on a fresh resource group (e.g. first prod deploy). + dependsOn: [keyVault] } resource keyVaultRes 'Microsoft.KeyVault/vaults@2023-07-01' existing = { diff --git a/packages/core/Dockerfile b/packages/core/Dockerfile index c8f670e..9abd0d6 100644 --- a/packages/core/Dockerfile +++ b/packages/core/Dockerfile @@ -19,7 +19,7 @@ RUN echo 'node-linker=hoisted' >> .npmrc # Inject Azure Artifacts auth token; strip it after install so the layer has no creds ARG AZURE_ARTIFACTS_TOKEN RUN if [ -n "${AZURE_ARTIFACTS_TOKEN}" ]; then \ - echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/python/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ + echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/node/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ fi RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install RUN sed -i '/_authToken/d' .npmrc diff --git a/packages/frontend/Dockerfile b/packages/frontend/Dockerfile index cd30d9b..16ed20c 100644 --- a/packages/frontend/Dockerfile +++ b/packages/frontend/Dockerfile @@ -12,7 +12,7 @@ COPY packages/shared/package.json packages/shared/ # Inject Azure Artifacts auth token; strip it after install so the layer has no creds ARG AZURE_ARTIFACTS_TOKEN RUN if [ -n "${AZURE_ARTIFACTS_TOKEN}" ]; then \ - echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/python/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ + echo "//pkgs.dev.azure.com/agentaflow/agentbase/_packaging/node/npm/registry/:_authToken=${AZURE_ARTIFACTS_TOKEN}" >> .npmrc; \ fi RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install RUN sed -i '/_authToken/d' .npmrc