セキュリティにテンプレートを使用する

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

この記事では、テンプレートを使用して Azure Pipelines のセキュリティを効率化する方法について説明します。 テンプレートは、パイプラインの外部構造を定義し、悪意のあるコード侵入を防ぐのに役立ちます。 テンプレートには、資格情報のスキャンなどのタスクを実行する手順を自動的に含めることもできます。 チームまたは組織内の複数のパイプラインが同じ構造を共有している場合は、テンプレートの使用を検討してください。

保護されたリソースをチェック Azure Pipelines の基本的なセキュリティ フレームワークを形成します。 これらのチェックは、パイプラインの構造、ステージ、ジョブに関係なく適用されます。 テンプレートを使用して、これらのチェックを適用できます。

テンプレートを含める、拡張する

Azure Pipelines には、および extends テンプレートが用意されています。

  • テンプレートには、C++ の #include と同様に、テンプレートを参照する外部ファイルに直接テンプレートのコードが含まれます。 次のパイプラインの例では、steps セクションにinclude-npm-steps.yml テンプレートを挿入します。

      steps:
      - template: templates/include-npm-steps.yml 
    
  • テンプレートを拡張して、パイプラインの外部構造を定義し、対象を絞ったカスタマイズのための特定のポイントを提供します。 C++ のコンテキストでは、 extends テンプレートは継承に似ています。

extends テンプレートを使用する場合は、テンプレートと最終的なパイプラインの両方でincludesを使用して、一般的な構成を行うこともできます。 完全なリファレンスについては、 Template 使用法のリファレンスを参照してください。

テンプレートを拡張する

最も安全なパイプラインの場合は、まず拡張テンプレートを使用します。 これらのテンプレートは、パイプラインの外部構造を定義し、悪意のあるコードがパイプラインに侵入するのを防ぎます。

たとえば、次のテンプレート ファイルの名前は template.yml です。

parameters:
- name: usersteps
  type: stepList
  default: []
steps:
- ${{ each step in parameters.usersteps }}:
  - ${{ step }}

次のパイプラインは、 template.yml テンプレートを拡張します。

# 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 エージェントが提供するサービスをユーザーステップに制限できます。 ユーザー ステップは、標準出力に出力される特別な形式の文字列である ログ コマンドを使用してサービスを要求します。 制限モードでは、成果物のアップロードやテスト結果の添付など、エージェントのサービスのほとんどは使用できません。

次の例のタスクは、 target プロパティが成果物の発行を許可しないようにエージェントに指示するため失敗します。

- task: PublishBuildArtifacts@1
  inputs:
    artifactName: myartifacts
  target:
    commands: restricted

restricted モードでは、setvariable コマンドは引き続き許容されるため、パイプライン変数は環境変数として後続のタスクにエクスポートされるため、注意が必要です。 REST API を介して取得された未解決の問題など、ユーザーが指定したデータを出力するタスクは、インジェクション攻撃に対して脆弱である可能性があります。 悪意のあるユーザー コンテンツは、エージェント ホストを侵害するために悪用される可能性のある環境変数を設定する可能性があります。

このリスクを軽減するために、パイプライン作成者は、 setvariable ログ コマンドを使用して、設定可能な変数を明示的に宣言できます。 空のリストを指定すると、すべての変数設定は許可されません。

次の例のタスクは、 expectedVar 変数または 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

構文の変更

Azure Pipelines テンプレートには、YAML 構文を反復処理して変更する柔軟性があります。 イテレーションを使用すると、特定の YAML セキュリティ機能を適用できます。

テンプレートでは、承認されたタスクのみを実行できるように、ユーザー ステップを書き換えることもできます。 たとえば、インライン スクリプトの実行を防ぐことができます。

次のテンプレート例では、ステップの種類 bashpowershellpwsh、および script が実行されないようにします。 アドホック スクリプトの完全なロックダウンのために、 BatchScriptShellScriptをブロックすることもできます。

# 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 following lines 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

テンプレートの手順

テンプレートは、パイプラインにステップを自動的に含めることができます。 これらの手順では、資格情報のスキャンや静的コード チェックなどのタスクを実行できます。 次のテンプレートは、すべてのジョブのユーザー ステップの前後にステップを挿入します。

parameters:
  jobs: []

jobs:
- ${{ each job in parameters.jobs }}: 
  - ${{ each pair in job }}:  
      ${{ if ne(pair.key, 'steps') }}:
        ${{ pair.key }}: ${{ pair.value }}
    steps:                            
    - task: CredScan@1 
    - ${{ job.steps }} 
    - task: PublishMyTelemetry@1 
      condition: always()

テンプレートの強制

テンプレートは重要なセキュリティ メカニズムですが、その有効性は適用に依存します。 テンプレートの使用を強制するための重要な制御ポイントは、 保護されたリソースです。 承認を構成し、エージェント プールまたはその他の保護されたリソース (リポジトリなど) を確認できます。 例については、「リポジトリ リソース チェックの追加」を参照してください。

必要なテンプレート

特定のテンプレートの使用を強制するには、リソースの 必要なテンプレート チェック を構成します。 このチェックは、パイプラインがテンプレートから拡張されている場合にのみ適用されます。

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

失敗した承認チェックを示すスクリーンショット。

必要なテンプレートを使用すると、チェックに合格します。

成功した承認チェックを示すスクリーンショット。

次の 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 }}

次のパイプライン例では、 params.yml テンプレートを拡張し、承認を求めます。 パイプラインエラーを示すには、 params.ymlへの参照をコメント アウトします。

# azure-pipeline.yml

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

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