Seguridad mediante plantillas

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

Las comprobaciones en los recursos protegidos son el bloque de creación básico de seguridad en Azure Pipelines. Las comprobaciones funcionan con independencia de la estructura (las fases y los trabajos) de la canalización. Si varias canalizaciones de su equipo u organización tienen la misma estructura, puede simplificar aún más la seguridad mediante plantillas.

Azure Pipelines ofrece dos tipos de plantillas: inclusiones y extensiones. Las plantillas incluidas se comportan como #include en C++: como si pegara el código de la plantilla directamente en el archivo externo, que hace referencia a él. Por ejemplo, aquí una plantilla de inclusiones (include-npm-steps.yml) se inserta en steps.

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

Para continuar con la metáfora de C++, las plantillas extends son más parecidas a la herencia: la plantilla proporciona la estructura externa de la canalización y un conjunto de lugares donde el consumidor de la plantilla puede realizar modificaciones específicas.

Uso de plantillas de extensiones

En el caso de las canalizaciones más seguras, se recomienda empezar con plantillas extends. Al proporcionar la estructura externa, una plantilla puede impedir que el código malintencionado entre en la canalización. De todos modos, podrá seguir usando includes, tanto en la plantilla como en la canalización final, para factorizar partes comunes de la configuración. Para usar una plantilla de extensiones, la canalización podría tener un aspecto similar al del ejemplo siguiente.

# 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

Al configurar plantillas extends, considere la posibilidad de anclarlas a una rama o etiqueta de Git determinada. De este modo, si es necesario realizar cambios importantes, las canalizaciones existentes no se verán afectadas. En los ejemplos anteriores se usa esta característica.

Características de seguridad aplicadas mediante YAML

Hay varias protecciones integradas en la sintaxis YAML y una plantilla de extensión puede aplicar el uso de cualquiera o todos ellos.

Destinos de paso

Restrinja algunos pasos para que se ejecuten en un contenedor en lugar del host. Sin acceso al host del agente, los pasos de usuario no pueden modificar la configuración del agente ni dejar código malintencionado para su ejecución posterior. Ejecute el código en el host primero para que el contenedor sea más seguro. Por ejemplo, se recomienda limitar el acceso a la red. Sin acceso abierto a la red, los pasos del usuario no podrán acceder a paquetes de orígenes no autorizados o cargar código y secretos en una ubicación de red.

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

Restricciones de comandos de registro de agentes

Restrinja los servicios que proporcionará el agente de Azure Pipelines a los pasos del usuario. Los pasos solicitan servicios mediante "comandos de registro" (cadenas con formato especial impresas en stdout). En el modo restringido, la mayoría de los servicios del agente, como cargar artefactos y asociar resultados de pruebas, no están disponibles.

# 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 de los comandos todavía permitidos en modo restringido es setvariable. Dado que las variables de canalización se exportan como variables de entorno a tareas posteriores, las tareas que generan datos proporcionados por el usuario (por ejemplo, el contenido de problemas abiertos recuperados de una API REST) pueden ser vulnerables a ataques por inyección. Este contenido de usuario puede establecer variables de entorno que, a su vez, se pueden usar para aprovechar el host del agente. Para evitar esto, los autores de canalizaciones pueden declarar explícitamente qué variables se pueden establecer a través del comando de registro setvariable. Especificar una lista vacía no permite establecer todas las variables.

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

Inserción condicional de fases o trabajos

Restrinja las fases y los trabajos para que se ejecuten en condiciones específicas. Las condiciones pueden ayudar, por ejemplo, a asegurarse de que solo compila determinadas ramas.

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

Exigencia de cierta sintaxis con plantillas de extensiones

Las plantillas pueden recorrer en iteración cualquier sintaxis YAML y modificarla o rechazarla. La iteración puede forzar el uso de una sintaxis YAML determinada, incluidas las características anteriores.

Una plantilla puede volver a escribir pasos de usuario y permitir que solo se ejecuten determinadas tareas aprobadas. Por ejemplo, puede evitar la ejecución de scripts insertados.

Advertencia

En el ejemplo siguiente, se impide que se ejecuten los pasos "bash", "powershell", "pwsh" y "script". Para el bloqueo completo de scripts ad hoc, también tendría que bloquear "BatchScript" y "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

Parámetros con seguridad de tipos

Las plantillas y sus parámetros se convierten en constantes antes de que se ejecute la canalización. Los parámetros de plantilla proporcionan seguridad de tipos a los parámetros de entrada. Por ejemplo, se puede restringir qué grupos se pueden usar en una canalización ofreciendo una enumeración de posibles opciones en lugar de una cadena de formato libre.

# 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

Establecimiento de las plantillas necesarias

Para exigir que se use una plantilla específica, puede establecer la comprobación de plantilla necesaria para un recurso o entorno. La comprobación de la plantilla necesaria se puede usar al realizar extensiones desde una plantilla.

Puede comprobar el estado de una comprobación al ver un trabajo de canalización. Cuando una canalización no se extiende desde la plantilla necesaria, se producirá un error en la comprobación y la ejecución se detendrá. Verá que se produjo un error en la comprobación.

Error en la comprobación de aprobación

Cuando se use la plantilla necesaria, verá que se ha superado la comprobación.

Paso del control de aprobación

Aquí se requiere la plantilla params.yml con una aprobación en el recurso. Para desencadenar un error en la canalización, convierta en comentario la referencia 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'

Pasos adicionales

Una plantilla puede agregar pasos sin que el autor de la canalización tenga que incluirlos. Estos pasos se pueden usar para ejecutar el examen de credenciales o comprobaciones de código estático.

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

Aplicación de plantillas

Una plantilla es solo un mecanismo de seguridad si puede aplicarla. El punto de control para aplicar el uso de plantillas es un recurso protegido. Puede configurar aprobaciones y comprobaciones en el grupo de agentes u otros recursos protegidos, como repositorios. Para ver un ejemplo, consulte Adición de una comprobación de recursos de repositorio.

Pasos siguientes

A continuación, aprenda a tomar entradas de forma segura mediante variables y parámetros.