Search code examples
dockerazure-pipelinesazure-keyvaultazure-pipelines-yaml

Credentials not being passed from Azure Keyvault correctly during docker image build


I have a python script that uses the DefaultAzureCredential() function provided by the azure.identity library. This works fine locally when I'm logged into my Azure account that has the correct permissions and the script runs.

I'm now wanting to run the script from inside a docker container by using the environment variables stored in my key vault. As far as I can see, this should work. The image is built and pushed to my Azure container registry through a managed identity. However, when I pull the image and try to run it, I receive the following error:

default_credential = DefaultAzureCredential()
                         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/azure/identity/_credentials/default.py", line 149, in __init__
    credentials.append(EnvironmentCredential(authority=authority, _within_dac=True, **kwargs))
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/azure/identity/_credentials/environment.py", line 70, in __init__
    self._credential = ClientSecretCredential(
                       ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/azure/identity/_credentials/client_secret.py", line 45, in __init__
    raise ValueError("client_id should be the id of a Microsoft Entra application")
ValueError: client_id should be the id of a Microsoft Entra application

My yaml file looks like this:

parameters:
- name: KVConnection
  type: string
  default: 'read-keyvault'
- name: keyVault
  type: string
  default: 'bi-pv-kv'
- name: containerRegistry
  type: string
  default: 'dcr'
- name: CRconnection
  type: string
  default: 'buildandpushdocker'

steps:
- task: AzureKeyVault@2
  displayName: "Get KV secrets"
  inputs:
    azureSubscription: ${{ parameters.KVConnection }}
    KeyVaultName: ${{ parameters.keyVault }}
    SecretsFilter: '*'
    RunAsPreJob: true
  
- bash: |
    echo "##vso[task.setvariable variable=clientid]$(sp-client-id)"
    echo "##vso[task.setvariable variable=clientcred]$(sp-az-secret)"
    echo "##vso[task.setvariable variable=tenantid]$(sp-tenant-id)"
  displayName: Set environment variables

- task: Docker@2
  displayName: Build Planview image 
  inputs: 
    command: build
    repository: planview
    containerRegistry: ${{ parameters.containerRegistry }}
    identity: ${{ parameters.CRconnection }}
    arguments: '--build-arg AZURE_CLIENT_ID=$(clientid) --build-arg AZURE_TENANT_ID=$(tenantid)
      --build-arg AZURE_CLIENT_SECRET=$(clientcred)'
    tags: | 
      planview

- task: Docker@2
  displayName: Push Planview image 
  inputs: 
    command: push
    repository: planview
    containerRegistry: ${{ parameters.containerRegistry }}
    identity: ${{ parameters.CRconnection }}
    tags: |
      planview

Is there a glaring error in there or how can it be improved? The service connections are all added correctly. I have tried hardcoding the exact credentials from the vault into a local dockerfile that ran locally and it also worked there, suggesting the problem is really due to the 3 secrets not being handled correctly.

Any suggestions or help is massively appreciated.


Solution

  • As far as I have tested based on your pipeline workflow, the issue does not seem to stem from the pipeline itself. I recommend you double-check how your Python script reads the environment variables defined in your Dockerfile.

    For your reference, I have provided my sample files, which successfully printed the expected environment variable values when I ran the container.

    azure-pipelines.yml

    trigger: none
    
    parameters:
    - name: KVConnection
      type: string
      default: 'ARMSvcCnnWIFAutoSub1'
    - name: keyVault
      type: string
      default: 'azkeyvaultxxxxxx '
    - name: containerRegistry
      type: string
      default: 'DockerHubSvcCnn'
    # - name: CRconnection
    #   type: string
    #   default: 'buildandpushdocker'
    
    steps:
    - task: AzureKeyVault@2
      displayName: "Get KV secrets"
      inputs:
        azureSubscription: ${{ parameters.KVConnection }}
        KeyVaultName: ${{ parameters.keyVault }}
        SecretsFilter: '*'
        RunAsPreJob: true
    - bash: |
        echo "##vso[task.setvariable variable=clientid]$(sp-client-id)"
        echo "##vso[task.setvariable variable=clientcred]$(sp-az-secret)"
        echo "##vso[task.setvariable variable=tenantid]$(sp-tenant-id)"
      displayName: Set environment variables
    - task: Docker@2
      displayName: Build Planview image 
      inputs: 
        command: build
        repository: azdockerhubxxx/planview
        containerRegistry: ${{ parameters.containerRegistry }}
        # identity: ${{ parameters.CRconnection }}
        arguments: '--build-arg AZURE_CLIENT_ID=$(clientid) --build-arg AZURE_TENANT_ID=$(tenantid) --build-arg AZURE_CLIENT_SECRET=$(clientcred)'
        tags: | 
          planview
    - task: Docker@2
      displayName: Push Planview image 
      inputs: 
        command: push
        repository: azdockerhubxxx/planview
        containerRegistry: ${{ parameters.containerRegistry }}
        # identity: ${{ parameters.CRconnection }}
        tags: |
          planview
    
    
    

    print_env.py

    import os
    
    def print_env_variables():
        client_id = os.getenv('AZURE_CLIENT_ID')
        tenant_id = os.getenv('AZURE_TENANT_ID')
        client_cred = os.getenv('AZURE_CLIENT_SECRET')
    
        print(f"Client ID: {client_id}")
        print(f"Tenant ID: {tenant_id}")
        print(f"Client Secret: {client_cred}")
    
    if __name__ == "__main__":
        print_env_variables()
    

    Dockerfile

    # Base image with Python
    FROM python:3.9-slim
    
    # Set environment variables (will be replaced by build arguments)
    ARG AZURE_CLIENT_ID
    ARG AZURE_TENANT_ID
    ARG AZURE_CLIENT_SECRET
    
    # Set them as environment variables inside the container
    ENV AZURE_CLIENT_ID=$AZURE_CLIENT_ID
    ENV AZURE_TENANT_ID=$AZURE_TENANT_ID
    ENV AZURE_CLIENT_SECRET=$AZURE_CLIENT_SECRET
    
    # Copy the Python script into the container
    COPY print_env.py /app/print_env.py
    
    # Set the working directory
    WORKDIR /app
    
    # Run the Python script to verify environment variables
    RUN python print_env.py
    
    # Default command for the image
    CMD ["python", "print_env.py"]
    

    Image