top of page

Azure DevOps a AWS con AWS Toolkit + Service Connection

  • victoriagimenez5
  • hace 3 días
  • 5 Min. de lectura
azure-devops-a-aws

Basado en la implementación real del proyecto + referencias oficialesAzure DevOps NO soporta OIDC nativo hacia AWS, aunque a nivel teórico se puede emitir un token,


  • NO es utilizable para escritura,

  • NO funciona con SigV4,

  • NO es un método soportado oficialmente por AWS.


Por eso:

  • Tuvimos que instalar el AWS Toolkit

  • Tuvimos que crear un AWS Service Connection

  • Recién ahí Azure Pipelines pudo escribir en AWS (ECR/Mira, S3, Terraform, etc.)


Esto es exactamente lo que hicimos en un proyecto y coincide 100% con ambos vendors (AWS y Microsoft Azure).


1. Verificación oficial: Azure DevOps NO soporta federación OIDC hacia AWS


Microsoft lo dice en forma explícita:

Azure DevOps AWS Service Connection → Requiere Access Key o STS con AWS Toolkit


Texto clave:

“To connect to AWS, you must provide an AWS Access Key ID and Secret Access Key.”


No menciona OIDC en ningún lugar.

AWS también confirma que Azure DevOps se conecta vía AWS Toolkit, no vía OIDC:

“Authentication is performed using AWS service connections… configured with Access Keys or used to assume roles.”


2. Instalar el AWS Toolkit en Azure DevOps


(Exactamente como hicimos con el cliente)

  1. Ir a Azure DevOps.

  2. Navegar a Organization Settings → Extensions → Browse Marketplace.

  3. Buscar “AWS Toolkit for Azure DevOps”.

  4. Instalarlo en la organización o en el proyecto.


ree

Documentación oficial:


3. Crear un AWS Service Connection (paso crítico)

Una vez instalado el AWS Toolkit, Azure DevOps habilita un nuevo tipo de conexión llamado AWS, que es necesario para que los pipelines puedan autenticarse correctamente contra AWS usando AssumeRole y generar firmas SigV4 para operaciones de escritura (ECR, S3, Terraform, ECS, etc.).


3.1. Crear una nueva Service Connection

Ir a:

Project Settings → Service connections → New service connection

En la ventana emergente seleccionar:

AWS (provisto por AWS Toolkit)


ree

3.2. Completar los datos requeridos por el AWS Toolkit

El formulario pedirá:

  • Role ARN que Azure DevOps debe asumir

  • External ID (si se utiliza en la Trust Policy, recomendado)

  • Default AWS Region

  • Connection name

  • Scope: Project-scoped o Organization-scoped

Luego hacer clic en Verify.

Si la trust policy está correcta en AWS, la conexión mostrará:

Verified successfully

Esto valida que Azure DevOps puede invocar sts:AssumeRole.


ree


ree

Nota: La opción “Use OIDC” que aparece en el formulario no establece un flujo de federación OIDC completo hacia AWS. Azure DevOps no soporta OIDC para firmar solicitudes AWS SigV4. Por eso, aunque el campo aparece disponible, NO debe usarse para este escenario y la forma soportada es mediante AssumeRole a través del AWS Toolkit.


3.3. Dónde ver la Service Connection una vez creada


Una vez creada, aparece listada en:

Project Settings → Service connections

Aquí se pueden ver:

  • Tipo de conexión

  • Nombre asignado

  • Descripción

  • Historial de uso

  • Configuración de aprobaciones


ree


4. Crear un IAM Role para Azure DevOps (AssumeRole)

Esto sí lo hicimos en AWS.

La trust policy fue de este estilo:

{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Effect": "Allow",
     "Principal": {
       "AWS": "arn:aws:iam::<ACCOUNT_ID>:user/<TOOLKIT-USER>"
     },
     "Action": "sts:AssumeRole",
     "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<EXTERNAL_ID_GENERADO>"
      }
     }
   }
 ]
}

Nota: <TOOLKIT-USER> es un IAM User creado por el cliente únicamente para ser utilizado por el AWS Toolkit dentro de Azure DevOps. No es generado automáticamente.


4.1. Código de terraform para crear la policy


# Policy para ECR Push desde Azure DevOps
resource "aws_iam_role_policy" "azure_devops_ecr_policy" {
 name = "ECR-Push-Policy"
 role = aws_iam_role.azure_devops_ecr.id


 policy = jsonencode({
   Version = "2012-10-17"
   Statement = [
     # ECR push
     {
       Effect = "Allow"
       Action = [
         "ecr:GetAuthorizationToken",
         "ecr:BatchCheckLayerAvailability",
         "ecr:InitiateLayerUpload",
         "ecr:UploadLayerPart",
         "ecr:CompleteLayerUpload",
         "ecr:PutImage"
       ]
       Resource = "*"
     },


     # STS caller identity (para validación en pipeline)
     {
       Effect = "Allow"
       Action = [
         "sts:GetCallerIdentity"
       ]
       Resource = "*"
     },


     # SSM Read-only parameters
     {
       Effect = "Allow"
       Action = [
         "ssm:GetParameter",
         "ssm:GetParameters",
         "ssm:GetParametersByPath"
       ]
       Resource = [
         "arn:aws:ssm:${local.region}:${data.aws_caller_identity.current.id}:parameter/${local.project}/${local.environment}/*",
         "arn:aws:ssm:${local.region}:${data.aws_caller_identity.current.id}:parameter/${local.project}-star/${local.environment}/*"
       ]
     },


     # S3 as needed
     {
       Effect = "Allow"
       Action = [
         "s3:PutObject",
         "s3:GetObject",
         "s3:ListBucket",
         "s3:GetBucketLocation"
       ]
       Resource = [
         "arn:aws:s3:::los-archivos-data-temp",
         "arn:aws:s3:::los-archivos-data-temp/*"
       ]
     }
   ]
 })
}

5. Paso 4 – Asignar permisos al Role para que Azure pueda escribir en AWS


Ejemplos usados por nosotros:

Ejemplo de Permisos para ECR:


{
 "Effect": "Allow",
 "Action": [
   "ecr:GetAuthorizationToken",
   "ecr:BatchCheckLayerAvailability",
   "ecr:PutImage",
   "ecr:InitiateLayerUpload",
   "ecr:UploadLayerPart",
   "ecr:CompleteLayerUpload"
 ],
 "Resource": "*"
}

Permisos para Terraform:

  • IAM

  • S3 backend

  • DynamoDB Lock

  • ECS/ECR updates


6. Paso 5 –  Consumir la Service Connection desde YAML

Ejemplo típico:

- task: AWSCLI@1
 inputs:
   awsCredentials: 'aws-connection-name'
   regionName: 'us-east-1'
   awsCommand: 'sts'
   awsSubCommand: 'get-caller-identity'
 displayName: "Validar identidad en AWS"

Esto fue lo que usamos para validar la conexión.


ree


7. Ejemplo Sintetizado: Build & Push a AWS ECR usando la Service Connection


El siguiente pipeline muestra la forma recomendada de construir una imagen Docker y publicarla en AWS ECR utilizando la AWS Service Connection, sin Access Keys ni IAM Users. Todo se basa en AssumeRole a través del AWS Toolkit.:


# Ejemplo simplificado de Build & Push a AWS ECR con Azure DevOps


trigger:
 branches:
   include:
     - master
pool:
 vmImage: 'ubuntu-latest'


variables:
 - name: AWS_REGION
   value: 'eu-central-1'
 - name: ECR_ACCOUNT_ID
   value: '123456789'
 - name: IMAGE_NAME
   value: 'prod/webapp'
steps:
 - checkout: self


 # Obtener token de SSM (opcional)
 - task: AWSShellScript@1
   displayName: 'Fetch NuGet Token from SSM'
   inputs:
     awsCredentials: 'AWS-serviceConnection'
     regionName: '$(AWS_REGION)'
     scriptType: 'inline'
     inlineScript: |
       TOKEN=$(aws ssm get-parameter --name "/serenity/production/VSS_NUGET_ACCESSTOKEN" --with-decryption --query 'Parameter.Value' --output text)
       echo "##vso[task.setvariable variable=NUGET_TOKEN;issecret=true]${TOKEN}"


 # Setear tag corto basado en el commit
 - script: |
     SHORT_SHA=$(echo "$(Build.SourceVersion)" | cut -c1-7)
     echo "##vso[task.setvariable variable=IMAGE_TAG]${SHORT_SHA}"
   displayName: "Set image tag"


 # Build & Push a ECR
 - task: AWSShellScript@1
   displayName: 'Build & Push Docker Image to ECR'
   inputs:
     awsCredentials: 'AWS-serviceConnection'
     regionName: '$(AWS_REGION)'
     scriptType: 'inline'
     inlineScript: |
       set -euxo pipefail
       REGISTRY="${ECR_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
       IMAGE_URI="${REGISTRY}/${IMAGE_NAME}"
       # Login a ECR usando STS + Role federado (sin credenciales)
       aws ecr get-login-password --region "${AWS_REGION}" | docker login --username AWS --password-stdin "${REGISTRY}"


       # Build
       docker build -f Dockerfile.webapp -t "${IMAGE_URI}:${IMAGE_TAG}" .
       # Tags
       docker tag "${IMAGE_URI}:${IMAGE_TAG}" "${IMAGE_URI}:latest"


       # Push
       docker push "${IMAGE_URI}:${IMAGE_TAG}"
       docker push "${IMAGE_URI}:latest"
   env:
     IMAGE_TAG: $(IMAGE_TAG)
     NUGET_TOKEN: $(NUGET_TOKEN)

7.1 Versión súper reducida (ideal si querés dejarlo en un solo bloque al final)


# Build & Push a AWS ECR usando AWS Toolkit + Service Connection
- task: AWSShellScript@1
 inputs:
   awsCredentials: 'AWS-serviceConnection'
   regionName: 'eu-central-1'
   scriptType: 'inline'
   inlineScript: |
     set -euxo pipefail
     REGISTRY="123456789.dkr.ecr.eu-central-1.amazonaws.com"
     IMAGE="${REGISTRY}/prod/webapp"
     TAG=$(echo "$(Build.SourceVersion)" | cut -c1-7)


     aws ecr get-login-password --region eu-central-1 \
       | docker login --username AWS --password-stdin "${REGISTRY}"


     docker build -t "${IMAGE}:${TAG}" .
     docker push "${IMAGE}:${TAG}"
     docker tag "${IMAGE}:${TAG}" "${IMAGE}:latest"
     docker push "${IMAGE}:latest"

La Seguridad primero


En resumen, este enfoque reproduce fielmente las mejores prácticas de seguridad recomendadas por AWS y la industria: evitar credenciales estáticas, eliminar IAM Users y reemplazar toda autenticación manual por federación basada en identidades. Al utilizar OIDC junto con el AWS Toolkit y un rol dedicado para Azure DevOps, los pipelines operan con credenciales temporales generadas por STS, firmadas automáticamente y con un alcance estrictamente controlado. Esto reduce la superficie de ataque, mejora la trazabilidad en CloudTrail y elimina por completo la necesidad de almacenar secretos sensibles en Azure DevOps.

En definitiva, este modelo no solo incrementa la seguridad del ciclo de despliegue, sino que también simplifica su operación diaria y se alinea directamente con el pilar Security del AWS Well-Architected Framework.



Nota sobre tokens de NuGet (para .NET en Azure DevOps)


En escenarios donde la aplicación se basa en .NET algo común en los entornos microsoft, Azure DevOps requiere un token privado de NuGet para restaurar paquetes desde su feed privado (nuget.org, Azure Artifacts, etc.). Este token no forma parte del proceso de autenticación contra AWS y debe obtenerse desde otro origen seguro. En nuestro pipeline, adoptamos una práctica coherente con el modelo “no-secrets-in-pipelines”:

El token se almacena en AWS Systems Manager Parameter Store, y Azure DevOps lo recupera dinámicamente a través del rol federado usando SSM GetParameter.

De esta manera, incluso los artefactos propios del ecosistema .NET pueden consumirse sin exponer secretos, manteniendo plena coherencia con la arquitectura de autenticación sin credenciales estáticas.



silvio-depetri



Silvio Depetri

Cloud Engineer

 
 
bottom of page