Security

Azure Key Vault Integration with Azure DevOps

Complete guide to integrating Azure Key Vault with Azure DevOps for centralized secrets management, covering variable groups, pipeline tasks, access policies, RBAC, and secret rotation strategies.

Azure Key Vault Integration with Azure DevOps

Overview

Azure Key Vault is where your secrets belong — not in pipeline variables, not in config files, not in anyone's notepad. Integrating Key Vault with Azure DevOps gives you centralized secrets management with audit logging, access policies, automatic rotation, and the ability to grant or revoke pipeline access to secrets without touching the pipeline YAML. I have migrated every team I have worked with away from plain pipeline variables to Key Vault because the security posture improvement is immediate and the integration effort is minimal.

Prerequisites

  • An Azure subscription with permissions to create and manage Key Vault resources
  • An Azure DevOps organization with at least one project and pipeline
  • An Azure service connection configured in Azure DevOps (Azure Resource Manager type)
  • Azure CLI installed locally for Key Vault setup
  • Node.js 16 or later for application code examples
  • Basic familiarity with Azure DevOps YAML pipelines and Azure RBAC

Setting Up Azure Key Vault for Pipeline Secrets

Start by creating a Key Vault and populating it with secrets your pipelines need.

# Create a resource group
az group create --name rg-devops-secrets --location eastus

# Create the Key Vault
az keyvault create \
  --name kv-devops-pipeline \
  --resource-group rg-devops-secrets \
  --location eastus \
  --sku standard \
  --enable-rbac-authorization true

# Add secrets
az keyvault secret set \
  --vault-name kv-devops-pipeline \
  --name "DatabaseConnectionString" \
  --value "Server=prod-db.postgres.database.azure.com;Database=appdb;User Id=appuser;Password=P@ssw0rd!2026;"

az keyvault secret set \
  --vault-name kv-devops-pipeline \
  --name "ApiKey" \
  --value "sk-prod-a1b2c3d4e5f6g7h8i9j0"

az keyvault secret set \
  --vault-name kv-devops-pipeline \
  --name "JwtSigningKey" \
  --value "super-secret-jwt-key-that-should-never-be-in-source-control"

# Verify
az keyvault secret list --vault-name kv-devops-pipeline --output table

Output:

Name                       ContentType    Enabled    Expires
-------------------------  -------------  ---------  ---------
DatabaseConnectionString                  true
ApiKey                                    true
JwtSigningKey                             true

Naming Conventions

Key Vault secret names only allow alphanumeric characters and hyphens. Choose a convention and stick with it:

# Good: clear, consistent, environment-prefixed
prod-database-connection-string
prod-api-key
staging-database-connection-string

# Bad: inconsistent, hard to filter
DB_CONN
myApiKey
production.jwt.secret

When secrets are pulled into pipeline variables, hyphens get converted to underscores. So prod-database-connection-string becomes $(prod-database-connection-string) in YAML but maps to the environment variable PROD_DATABASE_CONNECTION_STRING.

Key Vault Access Policies vs RBAC

Azure Key Vault supports two authorization models. Pick RBAC unless you have a specific reason not to.

Vault Access Policies (Legacy)

The original model. Permissions are assigned directly on the vault to specific identities.

# Grant the service principal read access to secrets
az keyvault set-policy \
  --name kv-devops-pipeline \
  --spn "your-service-principal-app-id" \
  --secret-permissions get list

Downsides: no conditional access, no fine-grained scope (it is all-or-nothing per vault), harder to audit, permissions do not show up in Azure RBAC tooling.

Azure RBAC (Recommended)

The modern model. Uses standard Azure role assignments, which means you get conditional access, PIM (Privileged Identity Management), and unified audit logs.

# Get the service principal object ID
SP_OBJECT_ID=$(az ad sp show --id "your-service-principal-app-id" --query id -o tsv)

# Assign Key Vault Secrets User role (read-only access to secrets)
az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee-object-id $SP_OBJECT_ID \
  --scope "/subscriptions/{sub-id}/resourceGroups/rg-devops-secrets/providers/Microsoft.KeyVault/vaults/kv-devops-pipeline"

Key roles for pipeline scenarios:

Role Permissions Use Case
Key Vault Secrets User Get, List secrets Pipeline reading secrets
Key Vault Secrets Officer Get, List, Set, Delete, Backup, Restore, Recover, Purge Admin managing secrets
Key Vault Certificates User Get, List certificates Pipeline using TLS certs
Key Vault Crypto User Encrypt, Decrypt, Sign, Verify, Wrap, Unwrap Pipeline cryptographic ops

Service Connection Permissions for Key Vault Access

The Azure DevOps service connection's service principal needs access to the Key Vault. This is the most common setup failure.

  1. In Azure DevOps, go to Project Settings > Service connections
  2. Select your Azure Resource Manager service connection
  3. Click Manage Service Principal to open it in Azure Portal
  4. Note the Application (client) ID and Object ID

Then grant the service principal access:

# Using RBAC (recommended)
az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee-object-id "service-principal-object-id" \
  --scope "/subscriptions/{sub-id}/resourceGroups/rg-devops-secrets/providers/Microsoft.KeyVault/vaults/kv-devops-pipeline"

# Verify the assignment
az role assignment list \
  --scope "/subscriptions/{sub-id}/resourceGroups/rg-devops-secrets/providers/Microsoft.KeyVault/vaults/kv-devops-pipeline" \
  --output table

If you are using vault access policies instead:

az keyvault set-policy \
  --name kv-devops-pipeline \
  --object-id "service-principal-object-id" \
  --secret-permissions get list

Variable Groups Linked to Key Vault

Variable groups are the cleanest way to pull Key Vault secrets into pipelines. The secrets stay in Key Vault — the variable group is just a reference.

Creating a Linked Variable Group

  1. Go to Pipelines > Library in Azure DevOps
  2. Click + Variable group
  3. Toggle Link secrets from an Azure key vault as variables
  4. Select the Azure service connection and the Key Vault name
  5. Click + Add and select which secrets to include
  6. Save the variable group

Or use the Azure DevOps CLI:

# Create the variable group linked to Key Vault
az pipelines variable-group create \
  --name "Production-Secrets" \
  --authorize true \
  --variables dummy=placeholder \
  --organization "https://dev.azure.com/your-org" \
  --project "YourProject"

Note: the CLI does not fully support Key Vault-linked variable groups yet. You will need to use the REST API or the portal for the Key Vault link. Here is the REST API approach:

// scripts/create-kv-variable-group.js
var https = require("https");

var ORG = process.env.AZURE_ORG;
var PROJECT = process.env.AZURE_PROJECT;
var PAT = process.env.AZURE_PAT;
var SERVICE_CONNECTION_ID = process.env.SERVICE_CONNECTION_ID;
var VAULT_NAME = process.env.VAULT_NAME;

var body = JSON.stringify({
    name: "Production-Secrets",
    type: "AzureKeyVault",
    providerData: {
        serviceEndpointId: SERVICE_CONNECTION_ID,
        vault: VAULT_NAME
    },
    variables: {
        "DatabaseConnectionString": {
            isSecret: true,
            value: null,
            enabled: true,
            contentType: ""
        },
        "ApiKey": {
            isSecret: true,
            value: null,
            enabled: true,
            contentType: ""
        },
        "JwtSigningKey": {
            isSecret: true,
            value: null,
            enabled: true,
            contentType: ""
        }
    }
});

var auth = Buffer.from(":" + PAT).toString("base64");
var options = {
    hostname: "dev.azure.com",
    path: "/" + ORG + "/" + PROJECT + "/_apis/distributedtask/variablegroups?api-version=7.1",
    method: "POST",
    headers: {
        "Authorization": "Basic " + auth,
        "Content-Type": "application/json",
        "Content-Length": Buffer.byteLength(body)
    }
};

var req = https.request(options, function(res) {
    var data = "";
    res.on("data", function(chunk) { data += chunk; });
    res.on("end", function() {
        if (res.statusCode >= 200 && res.statusCode < 300) {
            var result = JSON.parse(data);
            console.log("Variable group created: ID " + result.id);
            console.log("Name: " + result.name);
            console.log("Variables: " + Object.keys(result.variables).join(", "));
        } else {
            console.error("Error " + res.statusCode + ": " + data);
        }
    });
});

req.on("error", function(err) { console.error("Request failed:", err.message); });
req.write(body);
req.end();

Referencing Variable Groups in YAML

variables:
  - group: Production-Secrets

steps:
  - script: |
      echo "Connecting to database..."
      node deploy.js
    displayName: "Deploy with secrets"
    env:
      DB_CONNECTION: $(DatabaseConnectionString)
      API_KEY: $(ApiKey)
      JWT_KEY: $(JwtSigningKey)

Key Vault secrets are automatically masked in pipeline logs. If your code accidentally prints a secret value, Azure DevOps replaces it with ***.

The AzureKeyVault Pipeline Task

For more control over which secrets to fetch and when, use the AzureKeyVault@2 task directly:

steps:
  - task: AzureKeyVault@2
    displayName: "Fetch secrets from Key Vault"
    inputs:
      azureSubscription: "MyAzureServiceConnection"
      KeyVaultName: "kv-devops-pipeline"
      SecretsFilter: "DatabaseConnectionString,ApiKey,JwtSigningKey"
      RunAsPreJob: false

  - script: |
      echo "Database secret is available as $(DatabaseConnectionString)"
      node app.js
    env:
      DB_CONN: $(DatabaseConnectionString)
      API_KEY: $(ApiKey)

SecretsFilter Options

# Fetch all secrets (use sparingly — pulls everything)
SecretsFilter: "*"

# Fetch specific secrets (recommended)
SecretsFilter: "DatabaseConnectionString,ApiKey,JwtSigningKey"

# Fetch secrets matching a prefix (not natively supported — use *)
# Filter in a subsequent script step instead
SecretsFilter: "*"

RunAsPreJob

Set RunAsPreJob: true to fetch secrets before any other steps run. This is useful when other tasks need the secrets during initialization:

steps:
  - task: AzureKeyVault@2
    inputs:
      azureSubscription: "MyAzureServiceConnection"
      KeyVaultName: "kv-devops-pipeline"
      SecretsFilter: "*"
      RunAsPreJob: true

Fetching Certificates and Keys

Key Vault stores three types of objects: secrets, keys, and certificates. The AzureKeyVault@2 task only fetches secrets. For certificates and keys, use the Azure CLI task:

steps:
  - task: AzureCLI@2
    displayName: "Download TLS certificate"
    inputs:
      azureSubscription: "MyAzureServiceConnection"
      scriptType: "bash"
      scriptLocation: "inlineScript"
      inlineScript: |
        # Download certificate as PFX
        az keyvault certificate download \
          --vault-name kv-devops-pipeline \
          --name "tls-certificate" \
          --file $(Build.ArtifactStagingDirectory)/cert.pfx \
          --encoding PEM

        # Download the private key (stored as a secret with same name as cert)
        az keyvault secret show \
          --vault-name kv-devops-pipeline \
          --name "tls-certificate" \
          --query value -o tsv | base64 -d > $(Build.ArtifactStagingDirectory)/cert.key

        echo "Certificate downloaded to $(Build.ArtifactStagingDirectory)"

For cryptographic keys:

  - task: AzureCLI@2
    displayName: "Sign artifact with Key Vault key"
    inputs:
      azureSubscription: "MyAzureServiceConnection"
      scriptType: "bash"
      scriptLocation: "inlineScript"
      inlineScript: |
        # Create a hash of the artifact
        sha256sum $(Build.ArtifactStagingDirectory)/app.zip | awk '{print $1}' > hash.txt

        # Sign the hash using a Key Vault key
        az keyvault key sign \
          --vault-name kv-devops-pipeline \
          --name "signing-key" \
          --algorithm RS256 \
          --digest $(cat hash.txt) \
          --output tsv > signature.txt

        echo "Artifact signed"

Secret Rotation with Key Vault

Key Vault supports versioning — every time you update a secret, a new version is created. Pipelines always fetch the latest version unless you specify otherwise.

Manual Rotation

# Update a secret (creates a new version automatically)
az keyvault secret set \
  --vault-name kv-devops-pipeline \
  --name "DatabaseConnectionString" \
  --value "Server=prod-db.postgres.database.azure.com;Database=appdb;User Id=appuser;Password=N3wP@ssw0rd!2026;"

# The next pipeline run automatically picks up the new value
# No pipeline changes needed

Automated Rotation with Event Grid

Set up automated secret rotation using Azure Event Grid and an Azure Function:

// azure-function/rotate-secret/index.js
var https = require("https");

module.exports = function(context, eventGridEvent) {
    context.log("Secret rotation triggered:", eventGridEvent.subject);

    var secretName = eventGridEvent.subject.split("/").pop();
    var vaultName = eventGridEvent.data.VaultName;

    context.log("Rotating secret: " + secretName + " in vault: " + vaultName);

    // Generate new credential (example: rotate a database password)
    if (secretName === "DatabaseConnectionString") {
        var newPassword = generatePassword(32);

        // Update the database password first
        updateDatabasePassword(newPassword, function(err) {
            if (err) {
                context.log.error("Database password update failed:", err.message);
                context.done(err);
                return;
            }

            // Then update Key Vault with the new connection string
            var newConnString = "Server=prod-db.postgres.database.azure.com;Database=appdb;" +
                "User Id=appuser;Password=" + newPassword + ";";

            updateKeyVaultSecret(vaultName, secretName, newConnString, function(err2) {
                if (err2) {
                    context.log.error("Key Vault update failed:", err2.message);
                    context.done(err2);
                    return;
                }

                context.log("Secret rotated successfully");
                context.done();
            });
        });
    } else {
        context.log("No rotation handler for: " + secretName);
        context.done();
    }
};

function generatePassword(length) {
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
    var password = "";
    for (var i = 0; i < length; i++) {
        password += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return password;
}

function updateDatabasePassword(newPassword, callback) {
    // Implementation depends on your database provider
    // For Azure PostgreSQL, use the Azure Management API
    context.log("Updating database password...");
    callback(null);
}

function updateKeyVaultSecret(vaultName, secretName, value, callback) {
    // Use managed identity to authenticate to Key Vault
    // The Azure Function's managed identity needs Key Vault Secrets Officer role
    var body = JSON.stringify({ value: value });

    var options = {
        hostname: vaultName + ".vault.azure.net",
        path: "/secrets/" + secretName + "?api-version=7.4",
        method: "PUT",
        headers: {
            "Content-Type": "application/json",
            "Content-Length": Buffer.byteLength(body)
        }
    };

    // In production, get the Bearer token from managed identity
    // This is simplified — use @azure/identity in real code
    callback(null);
}

Configure the Event Grid subscription:

# Create Event Grid subscription for secret near-expiry events
az eventgrid event-subscription create \
  --name "secret-rotation" \
  --source-resource-id "/subscriptions/{sub-id}/resourceGroups/rg-devops-secrets/providers/Microsoft.KeyVault/vaults/kv-devops-pipeline" \
  --endpoint "/subscriptions/{sub-id}/resourceGroups/rg-devops-secrets/providers/Microsoft.Web/sites/func-secret-rotation/functions/rotate-secret" \
  --endpoint-type azurefunction \
  --included-event-types "Microsoft.KeyVault.SecretNearExpiry" "Microsoft.KeyVault.SecretExpired"

Set expiration dates on secrets to trigger rotation:

# Set a secret with a 90-day expiration
EXPIRY=$(date -u -d "+90 days" +%Y-%m-%dT%H:%M:%SZ)
az keyvault secret set \
  --vault-name kv-devops-pipeline \
  --name "ApiKey" \
  --value "sk-prod-new-key-value" \
  --expires "$EXPIRY"

# The SecretNearExpiry event fires 30 days before expiration by default

Complete Working Example

A multi-stage pipeline that uses Key Vault secrets across staging and production environments:

# azure-pipelines.yml
trigger:
  branches:
    include:
      - main

pool:
  vmImage: "ubuntu-latest"

variables:
  - group: Shared-Secrets
  - name: nodeVersion
    value: "20.x"

stages:
  - stage: Build
    displayName: "Build and Test"
    jobs:
      - job: BuildJob
        steps:
          - task: NodeTool@0
            inputs:
              versionSpec: $(nodeVersion)
            displayName: "Install Node.js"

          - script: npm ci
            displayName: "Install dependencies"

          - script: npm test
            displayName: "Run tests"

          - script: npm run build
            displayName: "Build application"

          - task: PublishPipelineArtifact@1
            inputs:
              targetPath: "$(Build.SourcesDirectory)/dist"
              artifact: "app-bundle"

  - stage: DeployStaging
    displayName: "Deploy to Staging"
    dependsOn: Build
    variables:
      - group: Staging-Secrets
    jobs:
      - deployment: StagingDeploy
        environment: staging
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureKeyVault@2
                  displayName: "Fetch staging secrets"
                  inputs:
                    azureSubscription: "Azure-Staging"
                    KeyVaultName: "kv-staging-pipeline"
                    SecretsFilter: "DatabaseConnectionString,ApiKey,RedisConnectionString"
                    RunAsPreJob: false

                - task: DownloadPipelineArtifact@2
                  inputs:
                    artifact: "app-bundle"
                    targetPath: "$(Pipeline.Workspace)/app"

                - task: AzureCLI@2
                  displayName: "Deploy to staging App Service"
                  inputs:
                    azureSubscription: "Azure-Staging"
                    scriptType: "bash"
                    scriptLocation: "inlineScript"
                    inlineScript: |
                      az webapp config appsettings set \
                        --name app-staging \
                        --resource-group rg-staging \
                        --settings \
                          DATABASE_URL="$(DatabaseConnectionString)" \
                          API_KEY="$(ApiKey)" \
                          REDIS_URL="$(RedisConnectionString)" \
                          NODE_ENV="staging"

                      az webapp deploy \
                        --name app-staging \
                        --resource-group rg-staging \
                        --src-path "$(Pipeline.Workspace)/app" \
                        --type zip

                - script: |
                    echo "Running smoke tests against staging..."
                    node scripts/smoke-test.js https://app-staging.azurewebsites.net
                  displayName: "Smoke test staging"
                  env:
                    API_KEY: $(ApiKey)

  - stage: DeployProduction
    displayName: "Deploy to Production"
    dependsOn: DeployStaging
    variables:
      - group: Production-Secrets
    jobs:
      - deployment: ProdDeploy
        environment: production
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureKeyVault@2
                  displayName: "Fetch production secrets"
                  inputs:
                    azureSubscription: "Azure-Production"
                    KeyVaultName: "kv-devops-pipeline"
                    SecretsFilter: "DatabaseConnectionString,ApiKey,JwtSigningKey,RedisConnectionString"
                    RunAsPreJob: false

                - task: DownloadPipelineArtifact@2
                  inputs:
                    artifact: "app-bundle"
                    targetPath: "$(Pipeline.Workspace)/app"

                - task: AzureCLI@2
                  displayName: "Deploy to production App Service"
                  inputs:
                    azureSubscription: "Azure-Production"
                    scriptType: "bash"
                    scriptLocation: "inlineScript"
                    inlineScript: |
                      az webapp config appsettings set \
                        --name app-production \
                        --resource-group rg-production \
                        --settings \
                          DATABASE_URL="$(DatabaseConnectionString)" \
                          API_KEY="$(ApiKey)" \
                          JWT_SIGNING_KEY="$(JwtSigningKey)" \
                          REDIS_URL="$(RedisConnectionString)" \
                          NODE_ENV="production"

                      az webapp deploy \
                        --name app-production \
                        --resource-group rg-production \
                        --src-path "$(Pipeline.Workspace)/app" \
                        --type zip

                - script: |
                    echo "Running production health check..."
                    node scripts/health-check.js https://app-production.azurewebsites.net
                  displayName: "Health check production"

Application code that reads secrets from environment variables set by the pipeline:

// config/secrets.js
var config = {
    database: {
        connectionString: process.env.DATABASE_URL || process.env.DB_CONNECTION || ""
    },
    api: {
        key: process.env.API_KEY || ""
    },
    jwt: {
        signingKey: process.env.JWT_SIGNING_KEY || ""
    },
    redis: {
        url: process.env.REDIS_URL || ""
    }
};

// Validate required secrets at startup
function validateSecrets() {
    var missing = [];
    if (!config.database.connectionString) { missing.push("DATABASE_URL"); }
    if (!config.api.key) { missing.push("API_KEY"); }

    if (missing.length > 0) {
        console.error("Missing required secrets: " + missing.join(", "));
        console.error("Ensure Key Vault secrets are configured and the pipeline fetches them.");
        process.exit(1);
    }

    console.log("All required secrets loaded successfully.");
}

module.exports = {
    config: config,
    validateSecrets: validateSecrets
};

Common Issues and Troubleshooting

"The user, group or application ... does not have secrets get permission on key vault"

##[error] Get secrets failed. Error: Could not fetch access token for Azure.
Status code: 403. Error: The user, group or application 'appid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;oid=yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' does not have secrets get permission on key vault 'kv-devops-pipeline;location=eastus'.

The service principal behind your Azure DevOps service connection does not have access to the Key Vault. If using RBAC, assign the Key Vault Secrets User role scoped to the vault. If using access policies, add a policy granting get and list permissions for secrets. Changes take up to 10 minutes to propagate.

"AzureKeyVault task: Could not fetch access token for Managed Service Identity"

##[error] Could not fetch access token for Managed Service Identity. Please configure Managed Service Identity for virtual machine 'https://management.azure.com/...'

This happens when using self-hosted agents without a system-assigned managed identity. Either switch to a Microsoft-hosted agent, configure a managed identity on the VM, or ensure the service connection uses a service principal (not managed identity) authentication.

Variable group shows "0 variables" after linking to Key Vault

You linked the variable group to Key Vault but did not add any secrets to it. Click + Add in the variable group settings, and you will see a list of secrets available in the vault. Select the ones you need. The variable group does not automatically include all secrets — you must explicitly choose which ones to expose to pipelines.

"Secret not found: MySecret" even though it exists in Key Vault

##[error] Secret not found: MySecret. Check that the secret exists in the vault 'kv-devops-pipeline' and the service principal has get permissions.

Secret names in Key Vault are case-sensitive. If the secret is named mySecret in Key Vault but you reference MySecret in the SecretsFilter, it will not match. Verify the exact secret name with az keyvault secret list --vault-name kv-devops-pipeline. Also check that the secret has not been disabled or soft-deleted.

Secrets work in classic pipelines but not in YAML

Classic release pipelines use a different authentication path for variable groups. In YAML pipelines, you must explicitly reference the variable group with - group: GroupName in the variables section. Also ensure the pipeline has been authorized to use the variable group — go to Pipelines > Library, select the group, and click the pipeline authorization lock icon.

Key Vault firewall blocks pipeline access

##[error] Client address is not authorized and caller is not a trusted service.

If you have Key Vault firewall rules enabled, Microsoft-hosted agents use dynamic IP addresses that change every run. Either add the AzureCloud service tag to the firewall exceptions, use a self-hosted agent with a static IP, or enable "Allow trusted Microsoft services to bypass this firewall" in Key Vault networking settings.

Best Practices

  • Use RBAC authorization instead of vault access policies. RBAC gives you conditional access, PIM support, and unified audit logs. Access policies are legacy and do not integrate with Azure AD Conditional Access or identity governance.

  • Create separate Key Vaults per environment. Do not store staging and production secrets in the same vault. Separate vaults mean separate access controls — a compromised staging service connection cannot read production secrets.

  • Use the SecretsFilter parameter to fetch only the secrets you need. Fetching all secrets with * is wasteful and increases the blast radius if a pipeline is compromised. List specific secret names.

  • Set expiration dates on all secrets. Even if you do not have automated rotation yet, expiration dates serve as a reminder. Key Vault raises SecretNearExpiry events 30 days before expiration, which you can wire to alerts or automation.

  • Reference secrets through variable groups, not inline task outputs. Variable groups provide a single point of management. Changing a secret in Key Vault automatically propagates to every pipeline using the variable group on the next run.

  • Never log or echo secret values in pipeline scripts. Azure DevOps masks known secret values in logs, but only if they match registered variables. Avoid string manipulation that might expose partial secret values.

  • Audit Key Vault access with Azure Monitor. Enable diagnostic logging on the Key Vault and send logs to a Log Analytics workspace. Query AzureDiagnostics | where ResourceType == "VAULTS" to see who accessed what and when.

  • Use managed identities where possible. If your pipeline deploys to Azure resources, prefer managed identity authentication over storing service principal credentials in Key Vault. This eliminates the need to manage the credential at all.

References

Powered by Contentful