Sicurezza tramite modelli

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

I controlli sulle risorse protette sono il blocco predefinito di base della sicurezza per Azure Pipelines. I controlli funzionano indipendentemente dalla struttura, ovvero le fasi e i processi, della pipeline. Se più pipeline nel team o nell'organizzazione hanno la stessa struttura, è possibile semplificare ulteriormente la sicurezza usando i modelli.

Azure Pipelines offre due tipi di modelli: include ed estende. I modelli inclusi si comportano come #include in C++: è come se si incollasse il codice del modello direttamente nel file esterno, che vi fa riferimento. Ad esempio, in questo caso viene inserito un modello include (include-npm-steps.yml) in steps.

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

Per continuare la metafora C++, extends i modelli sono più simili all'ereditarietà: il modello fornisce la struttura esterna della pipeline e un set di posizioni in cui il consumer di modelli può apportare modifiche mirate.

Usare modelli di estensione

Per le pipeline più sicure, è consigliabile iniziare con extends i modelli. Fornendo la struttura esterna, un modello può impedire che il codice dannoso entri nella pipeline. È comunque possibile usare includes, sia nel modello che nella pipeline finale, per tenere conto delle parti comuni della configurazione. Per usare un modello esteso, la pipeline potrebbe essere simile all'esempio seguente.

# 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

Quando si configurano extends i modelli, è consigliabile ancorarli a un determinato ramo o tag Git. In questo modo, se è necessario apportare modifiche di rilievo, le pipeline esistenti non saranno interessate. Gli esempi precedenti usano questa funzionalità.

Funzionalità di sicurezza applicate tramite YAML

Esistono diverse protezioni integrate nella sintassi YAML e un modello di estensione può imporre l'utilizzo di uno o di tutti.

Destinazioni dei passaggi

Limitare alcuni passaggi da eseguire in un contenitore anziché nell'host. Senza l'accesso all'host dell'agente, i passaggi utente non possono modificare la configurazione dell'agente o lasciare il codice dannoso per un'esecuzione successiva. Eseguire prima il codice nell'host per rendere il contenitore più sicuro. Ad esempio, è consigliabile limitare l'accesso alla rete. Senza l'accesso aperto alla rete, i passaggi utente non potranno accedere ai pacchetti da origini non autorizzate o caricare codice e segreti in un percorso di rete.

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

Restrizioni dei comandi di registrazione degli agenti

Limitare i servizi che l'agente di Azure Pipelines fornirà ai passaggi utente. I passaggi richiedono servizi usando "comandi di registrazione" (stringhe in formato speciale stampate in stdout). In modalità con restrizioni, la maggior parte dei servizi dell'agente, ad esempio il caricamento di artefatti e il collegamento dei risultati dei test, non sono disponibili.

# 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

Uno dei comandi ancora consentiti in modalità con restrizioni è il setvariable comando . Poiché le variabili della pipeline vengono esportate come variabili di ambiente nelle attività successive, le attività che generano dati forniti dall'utente (ad esempio, il contenuto dei problemi aperti recuperati da un'API REST) possono essere vulnerabili agli attacchi injection. Tali contenuti utente possono impostare variabili di ambiente che a loro volta possono essere usate per sfruttare l'host dell'agente. Per impedire questo problema, gli autori della pipeline possono dichiarare in modo esplicito quali variabili sono impostabili tramite il setvariable comando di registrazione. Se si specifica un elenco vuoto, non è consentito impostare tutte le variabili.

# 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"

Inserimento condizionale di fasi o processi

Limitare le fasi e i processi da eseguire in condizioni specifiche. Le condizioni possono essere utili, ad esempio, per assicurarsi di creare solo determinati rami.

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

Richiedere una sintassi specifica con i modelli di estensione

I modelli possono scorrere e modificare/impedire qualsiasi sintassi YAML. L'iterazione può forzare l'uso di una particolare sintassi YAML, incluse le funzionalità precedenti.

Un modello può riscrivere i passaggi utente e consentire l'esecuzione di determinate attività approvate. È ad esempio possibile impedire l'esecuzione di script inline.

Avviso

Nell'esempio seguente non è possibile eseguire i passaggi di tipo "bash", "powershell", "pwsh" e "script". Per il blocco completo degli script ad hoc, è anche necessario bloccare "BatchScript" e "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

Parametri indipendenti dai tipi

I modelli e i relativi parametri vengono trasformati in costanti prima dell'esecuzione della pipeline. I parametri del modello forniscono la sicurezza dei tipi ai parametri di input. Ad esempio, può limitare i pool che possono essere usati in una pipeline offrendo un'enumerazione delle opzioni possibili anziché una stringa a mano libera.

# 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

Impostare i modelli obbligatori

Per richiedere l'uso di un modello specifico, è possibile impostare il controllo del modello richiesto per una risorsa o un ambiente. Il controllo del modello richiesto può essere usato durante l'estensione da un modello.

È possibile controllare lo stato di un controllo durante la visualizzazione di un processo della pipeline. Quando una pipeline non si estende dal modello di richiesta, il controllo avrà esito negativo e l'esecuzione verrà arrestata. Si noterà che il controllo non è riuscito.

controllo approvazione non riuscito

Quando si usa il modello richiesto, si noterà che il controllo è stato superato.

il controllo dell'approvazione passa

In questo caso il modello params.yml è necessario con un'approvazione per la risorsa. Per attivare l'esito negativo della pipeline, impostare come commento il riferimento a 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'

Passaggi aggiuntivi

Un modello può aggiungere passaggi senza che l'autore della pipeline li includa. Questi passaggi possono essere usati per eseguire l'analisi delle credenziali o i controlli del codice statico.

# 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()

Passaggi successivi

Informazioni quindi sull'acquisizione sicura degli input tramite variabili e parametri.