Key Vault를 사용한 Azure Pipelines 비밀 관리

10423 단어

요약



이 글에서는 Azure Pipelines에서 IaC 파이프라인에 대한 변수로 암호를 입력한 다음 Azure Key Vault에만 안전하게 비밀을 보관하는 예제를 소개하고자 합니다. Azure 리소스를 배포하면 더 이상 비밀을 파이프라인 변수로 유지할 필요가 없습니다. 이렇게 하면 비밀 관리가 더 쉽고 효과적입니다.

샘플 코드: api-management-sample

목차


  • Context

  • IaC pipeline
  • Sequence
  • Key Vault stage
  • Bicep stage


  • 문맥

    Azure Pipeline Library allow you to use Variable Group to input secrets to the IaC pipeline. You can Link secrets from an Azure key vault 고정합니다. 그러나 분명히 첫 번째 실행에서 비밀을 저장하는 Key Vault가 필요하며 IaC 파이프라인의 첫 번째 실행에서 비밀을 설정하는 데는 작동하지 않습니다.

    작동하지 않습니다.


    또 다른 옵션은 Azure Pipeline Secret Variable입니다. 여기서 IaC 파이프라인 첫 번째 실행에 대한 암호를 안전하게 설정할 수 있습니다. 그러나 인프라 구성을 위해 IaC 파이프라인을 두 번째 이상 실행할 때 Pipeline Secret 변수에는 여전히 동일한 암호가 필요합니다. 시스템 작동을 위해 키 자격 증명 모음과 파이프라인 비밀 변수의 두 위치에 비밀을 유지해야 하기 때문에 이것은 내가 원하는 것이 아닙니다. 가장 안전하고 효과적인 비밀 관리는 작업을 위해 비밀을 Key Vault에만 저장하는 것입니다.

    두 곳에서 비밀을 지키는 건 내가 하고 싶은 일이 아니야



    내가 하고 싶은 것은 IaC 배포를 처음 실행하는 경우 Pipeline Secret Variable에 비밀을 설정하고 Pipeline Secret Variable에서 비밀을 제거하고 Key Vault에만 저장하는 것입니다. IaC 파이프라인은 두 번째 실행에 대한 파이프라인 비밀 변수의 비밀 입력 없이 오류에 직면합니다. 두 번째 실행을 위해 이 작업을 수행하는 데 필요한 것은 Key Vault에서 기존 비밀을 다운로드하고 Azure 파이프라인 변수로 설정하는 것입니다.

    이게 내가 하고 싶은 일이야


    IaC 파이프라인

    I am trying to explain how to make this work with the sample code api-management-sample . 샘플 코드는 해당 문서를 참조할 수 있습니다.


  • 순서


    Key Vault 단계

    • Two secret inputs of BASIC_AUTH_PASS and CLIENT_SECRET have to be set as Azure DevOps Pipeline Secret Variable.
    • In Key Vault stage, it uses for each statement for the repeatable tasks with two secret variables.
    • Parameters for the repeatable tasks have name , secret , kvsecretname .
    • secret is important to set as parameter so the pipeline can dynamically use the secret value in the powershell script.
    • In the powershell script, if ( '${{ PipelineSecret.secret }}' -like '*${{ PipelineSecret.name }}*' ) sees if secrets exist in Pipeline Secret Variable. This syntax is tricky, but if Pipeline Secret Variable does not exist, PipelineSecret.secret becomes $(BASIC_AUTH_PASS) or $(CLIENT_SECRET) . That is why like '*${{ PipelineSecret.name }}*' syntax is used.
    • Write-Host "##vso[task.setvariable variable=$env:SecretVariableName;issecret=true;isOutput=true]$secretValue" sets secrets as variables in this pipeline, which can be used in the later stage. issecret=true makes the variable as secure string, and isOutput=true allows to be used in the later stage.
    iac_pipeline_subscription.yml

    parameters:
    - name: PipelineSecrets
      type: object
      default:
        - name: BASIC_AUTH_PASS
          secret: $(BASIC_AUTH_PASS)
          kvsecretname: $(KVSECRET_NAME_BASIC_PASS)
        - name: CLIENT_SECRET
          secret: $(CLIENT_SECRET)
          kvsecretname: $(KVSECRET_NAME_CLIENTSECRET)
    
    stages:
    - stage: KeyVault
      jobs:
      - ${{ each PipelineSecret in parameters.PipelineSecrets }}:
        - job: ${{ PipelineSecret.name }}
          variables:
            - name: SecretVariableName
              value: ${{ PipelineSecret.name }}
          steps:
          - task: AzurePowerShell@5
            name: ${{ PipelineSecret.name }}
            displayName: Set ${{ PipelineSecret.name }} variable
            env:
              SecretValue: ${{ PipelineSecret.secret }}
            inputs:
              azureSubscription: $(AZURE_SVC_NAME)
              azurePowerShellVersion: latestVersion
              ScriptType: InlineScript
              Inline: |
                if ( '${{ PipelineSecret.secret }}' -like '*${{ PipelineSecret.name }}*' ){
                  $secretValue = Get-AzKeyVaultSecret -VaultName $env:KeyVaultName -Name '${{ PipelineSecret.kvsecretname }}' -AsPlainText
                  Write-Host "##vso[task.setvariable variable=$env:SecretVariableName;issecret=true;isOutput=true]$secretValue"
                  Write-Host "Input variable $env:SecretVariableName does not exists. $secretValue is set for the later stage."
                }
                else{
                  Write-Host "##vso[task.setvariable variable=$env:SecretVariableName;issecret=true;isOutput=true]'${{ PipelineSecret.secret }}'"
                  Write-Host "Input variable $env:SecretVariableName already exists. Use this variable for the later stage."
                }
    


    이두근 단계

  • This stage needs value: $[ stageDependencies.KeyVault.BASIC_AUTH_PASS.outputs['BASIC_AUTH_PASS.BASIC_AUTH_PASS'] ] to use variables set in the previous stage. You can refer the syntax to Use output variables from tasks
  • 비밀 변수는 env:로 설정해야 합니다. 구문은 Set secret variables에 설명되어 있습니다.

  • iac_pipeline_subscription.yml

    - stage: Deploy
      jobs:
      - job: AzureResourceGroupDeployment
        timeoutInMinutes: 0
        variables:
          - name: BasicAuthPass
            value: $[ stageDependencies.KeyVault.BASIC_AUTH_PASS.outputs['BASIC_AUTH_PASS.BASIC_AUTH_PASS'] ]
          - name: ClientSecret
            value: $[ stageDependencies.KeyVault.CLIENT_SECRET.outputs['CLIENT_SECRET.CLIENT_SECRET'] ]
          - name: CertThumbprint
            value: $[ stageDependencies.KeyVault.Certificate.outputs['TaskCertificate.CertThumbprint'] ]
        steps:
        - task: AzureCLI@2
          displayName: Deploy Azure resources
          env:
            BasicAuthPass: $(BasicAuthPass)
            ClientSecret: $(ClientSecret)
            CertThumbprint: $(CertThumbprint)
          inputs:
            azureSubscription: $(AZURE_SVC_NAME)
            scriptType: ps
            scriptLocation: inlineScript
            inlineScript: |
              az group create --name $(ResourceGroupName) --location $(LOCATION)
              az deployment group create --resource-group $(ResourceGroupName) --template-file $(BicepFilePath) `
                --parameters $(BicepParameterFilePath) `
                base_name=$(BASE_NAME) `
                environment_symbol=$(ENVIRONMENT_SYMBOL) `
                aad_objectid_svc=$(AAD_OBJECTID_SVC) `
                aad_appid_client=$(AAD_APPID_CLIENT) `
                aad_appid_backend=$(AAD_APPID_BACKEND) `
                aad_tenantid=$(AAD_TENANTID) `
                basic_auth_user=$(BASIC_AUTH_USER) `
                kvcert_name_api=$(KVCERT_NAME_API) `
                kvsecret_name_basic_pass=$(KVSECRET_NAME_BASIC_PASS) `
                kvsecret_name_cert_thumbprint=$(KVSECRET_NAME_CERT_THUMBPRINT) `
                kvsecret_name_clientsecret=$(KVSECRET_NAME_CLIENTSECRET) `
                kvsecret_name_subscription_key=$(KVSECRET_NAME_SUBSCRIPTION_KEY) `
                apim_api_name_ad=$(APIM_API_NAME_AD) `
                apim_api_path_ad=$(APIM_API_PASH_AD) `
                apim_api_name_basic=$(APIM_API_NAME_BASIC) `
                apim_api_path_basic=$(APIM_API_PASH_BASIC) `
                apim_api_name_cert=$(APIM_API_NAME_CERT) `
                apim_api_path_cert=$(APIM_API_PASH_CERT) `
                basic_auth_pass=$(BasicAuthPass) `
                client_secret=$(ClientSecret) `
                cert_thumbprint=$(CertThumbprint)
    

    좋은 웹페이지 즐겨찾기