テンプレートによるセキュリティ

Azure DevOps Services | Azure DevOps Server 2022 | Azure DevOps Server 2020

保護されたリソースのチェックは、Azure Pipelines のセキュリティの基本的な構成要素です。 チェックは、パイプラインの構造 (ステージとジョブ) に関係なく機能します。 チームまたは組織内の複数のパイプラインの構造が同じ場合は、テンプレートを使用すると、さらに簡単にセキュリティを確保できます。

Azure Pipelines には、インクルード拡張の 2 種類のテンプレートが用意されています。 インクルードされるテンプレートは、C++ の #include のように動作します。これは、テンプレートのコードをそのまま外部ファイルに貼り付けて、それを参照するような動作です。 たとえば、このインクルード テンプレート (include-npm-steps.yml) は、steps に挿入されます。

  steps:
  - template: templates/include-npm-steps.yml 

引き続き C++ の例で説明すると、extends テンプレートは継承に似ています。テンプレートは、パイプラインの外部構造と、テンプレート コンシューマーがターゲットを変更できる一連の場所を提供します。

拡張テンプレートを使用する

最も安全なパイプラインのためには、extends テンプレートから開始することをお勧めします。 外部構造を指定することで、テンプレートでは悪意のあるコードがパイプラインに侵入することを防ぐことができます。 テンプレートと最終的なパイプラインの両方で includes を使用することで、構成の共通部分を取り外すこともできます。 拡張テンプレートを使用する場合、パイプラインは次の例のようになります。

# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ step }}
# azure-pipelines.yml
resources:
  repositories:
  - repository: templates
    type: git
    name: MyProject/MyTemplates
    ref: refs/tags/v1

extends:
  template: template.yml@templates
  parameters:
    usersteps:
    - script: echo This is my first step
    - script: echo This is my second step

extends テンプレートを設定するときには、そのテンプレートを特定の Git ブランチまたはタグに固定することを検討してください。 そうすることで、破壊的変更が必要になった場合に、既存のパイプラインは影響を受けなくなります。 前述の例では、この機能を使用しています。

YAML によって適用されるセキュリティ機能

YAML 構文にはいくつかの保護機能が組み込まれており、拡張テンプレートでは、それらのいずれか、またはすべての使用を適用できます。

ステップ ターゲット

ホストではなくコンテナーで実行する一部のステップを制限します。 エージェントのホストにアクセスしないと、ユーザー ステップではエージェントの構成を変更することや、悪意のあるコードを後で実行するために残したりすることができなくなります。 コンテナーの安全性が向上するように、最初にホストでコードを実行します。 たとえば、ネットワークへのアクセスを制限することをお勧めします。 ネットワークへのオープン アクセスがないと、ユーザー ステップでは承認されていないソースからパッケージにアクセスしたり、コードとシークレットをネットワークの場所にアップロードしたりできなくなります。

resources:
  containers:
  - container: builder
    image: mysecurebuildcontainer:latest
steps:
- script: echo This step runs on the agent host, and it could use docker commands to tear down or limit the container's network
- script: echo This step runs inside the builder container
  target: builder

エージェント ログ コマンドの制限

Azure Pipelines エージェントがユーザー ステップに提供するサービスを制限します。 ステップは、"ログ コマンド" (stdout に出力される特別に書式設定された文字列) を使用してサービスを要求します。 制限モードでは、成果物のアップロードやテスト結果の添付など、エージェントのほとんどのサービスが使用できなくなります。

# this task will fail because its `target` property instructs the agent not to allow publishing artifacts
- task: PublishBuildArtifacts@1
  inputs:
    artifactName: myartifacts
  target:
    commands: restricted

制限モードでも使用できるコマンドの 1 つは、setvariable コマンドです。 パイプライン変数は後続のタスクに環境変数としてエクスポートされるため、ユーザー指定のデータを出力するタスク (REST API から取得された未解決の問題の内容など) は、インジェクション攻撃に対して脆弱になる可能性があります。 そのようなユーザー コンテンツは、エージェント ホストの悪用に使用できる環境変数を設定できます。 これを禁止するために、パイプライン作成者は setvariable ログ コマンドを使用して、設定可能な変数を明示的に宣言できます。 空のリストを指定すると、すべての変数の設定が禁止されます。

# this task will fail because the task is only allowed to set the 'expectedVar' variable, or a variable prefixed with "ok"
- task: PowerShell@2
  target:
    commands: restricted
    settableVariables:
    - expectedVar
    - ok*
  inputs:
    targetType: 'inline'
    script: |
      Write-Host "##vso[task.setvariable variable=BadVar]myValue"

ステージまたはジョブの条件付き挿入

特定の条件下でステージとジョブを実行するように制限します。 条件は、たとえば、特定のブランチのみをビルドする場合に役立ちます。

jobs:
- job: buildNormal
  steps:
  - script: echo Building the normal, unsensitive part
- ${{ if eq(variables['Build.SourceBranchName'], 'refs/heads/main') }}:
  - job: buildMainOnly
    steps:
    - script: echo Building the restricted part that only builds for main branch

拡張テンプレートを使用して特定の構文を要求する

テンプレートは反復して、YAML 構文を変更または禁止できます。 反復では、前述の機能を含む特定の YAML 構文の使用を強制できます。

テンプレートでは、ユーザー ステップを書き換えて、承認された特定のタスクの実行のみを許可することができます。 たとえば、インライン スクリプトの実行を防止できます。

警告

次の例では、"bash"、"powershell"、"pwsh"、"script" タイプのステップは実行できなくなります。 アドホック スクリプトを完全にロックダウンする場合は、"BatchScript" と "ShellScript" もブロックする必要があります。

# template.yml
parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ if not(or(startsWith(step.task, 'Bash'),startsWith(step.task, 'CmdLine'),startsWith(step.task, 'PowerShell'))) }}:  
    - ${{ step }}
  # The lines below will replace tasks like Bash@3, CmdLine@2, PowerShell@2
  - ${{ else }}:  
    - ${{ each pair in step }}:
        ${{ if eq(pair.key, 'inputs') }}:
          inputs:
            ${{ each attribute in pair.value }}:
              ${{ if eq(attribute.key, 'script') }}:
                script: echo "Script removed by template"
              ${{ else }}:
                ${{ attribute.key }}: ${{ attribute.value }}
        ${{ elseif ne(pair.key, 'displayName') }}:
          ${{ pair.key }}: ${{ pair.value }}

          displayName: 'Disabled by template: ${{ step.displayName }}'
# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    usersteps:
    - task: MyTask@1
    - script: echo This step will be stripped out and not run!
    - bash: echo This step will be stripped out and not run!
    - powershell: echo "This step will be stripped out and not run!"
    - pwsh: echo "This step will be stripped out and not run!"
    - script: echo This step will be stripped out and not run!
    - task: CmdLine@2
      displayName: Test - Will be stripped out
      inputs:
        script: echo This step will be stripped out and not run!
    - task: MyOtherTask@2

タイプ セーフ パラメーター

テンプレートとそのパラメーターは、パイプラインの実行前に定数に変換されます。 テンプレート パラメーター は、入力パラメーターに型セーフを提供します。 たとえば、パイプラインで使用可能なプールは、フリーフォーム文字列ではなく、選択可能なオプションの列挙を提供することで制限できます。

# template.yml
parameters:
- name: userpool
  type: string
  default: Azure Pipelines
  values:
  - Azure Pipelines
  - private-pool-1
  - private-pool-2

pool: ${{ parameters.userpool }}
steps:
- script: # ... removed for clarity
# azure-pipelines.yml
extends:
  template: template.yml
  parameters:
    userpool: private-pool-1

必須テンプレートを設定する

特定のテンプレートの使用を必須にするには、リソースまたは環境に対する必須テンプレートのチェックを設定します。 必須テンプレートのチェックは、テンプレートから拡張する場合に使用できます。

このチェックの状態は、パイプライン ジョブを表示するときに確認できます。 パイプラインが必須のテンプレートから拡張されない場合、チェックは失敗して、実行は停止します。 チェックに失敗したことが示されます。

承認チェックに失敗

必須テンプレートが使用されているときには、チェックに合格したことが示されます。

承認チェックに合格

このテンプレート params.yml は、リソースの承認で必須になります。 パイプラインの失敗をトリガーするには、params.yml への参照をコメントアウトします。

# params.yml
parameters:
- name: yesNo 
  type: boolean
  default: false
- name: image
  displayName: Pool Image
  type: string
  default: ubuntu-latest
  values:
  - windows-latest
  - ubuntu-latest
  - macOS-latest

steps:
- script: echo ${{ parameters.yesNo }}
- script: echo ${{ parameters.image }}
# azure-pipeline.yml

resources:
 containers:
     - container: my-container
       endpoint: my-service-connection
       image: mycontainerimages

extends:
    template: params.yml
    parameters:
        yesNo: true
        image: 'windows-latest'

追加の手順

テンプレートでは、パイプライン作成者がステップを含めなくてもステップを追加できます。 そうしたステップは、資格情報のスキャンや静的コードのチェックを実行するために使用できます。

# template to insert a step before and after user steps in every job
parameters:
  jobs: []

jobs:
- ${{ each job in parameters.jobs }}: # Each job
  - ${{ each pair in job }}:  # Insert all properties other than "steps"
      ${{ if ne(pair.key, 'steps') }}:
        ${{ pair.key }}: ${{ pair.value }}
    steps:                            # Wrap the steps
    - task: CredScan@1                # Pre steps
    - ${{ job.steps }}                # Users steps
    - task: PublishMyTelemetry@1      # Post steps
      condition: always()

テンプレートの強制

テンプレートは、それを強制できる場合にのみセキュリティ メカニズムになります。 テンプレートの使用を強制するコントロール ポイントは、保護されたリソースです。 エージェント プールやリポジトリなどの保護されたリソースに対して承認とチェックを構成できます。 例については、「リポジトリ リソース チェックの追加」を参照してください。

次のステップ

次に、変数とパラメーターを使用して安全に入力を取得する方法について説明します。