Pulumi, Github Actions y OICD
Hacia mucho tiempo que queria armar esto para mi blogcito, pero como siempre, el tiempo es tirano y nos pasa por arriba. Este finde decidi que necesitaba un break del estudio (si, sigo estudiando, siento que no avanzo super lento...) y le dedique un rato a armar esto.
Primero, unas aclaraciones
- Mi blog esta hecho en .NET. Mi proyecto de Pulumi, tambien. Asi que si usan otra cosa, obviamente el codigo no les va a servir, pero si les va a servir la logica general
- Yo usaba pulumi localmente. Entonces mi stack esta tanto local como en la nube. Por eso mismo, el nombre del profile que busca tiene que ser el mismo. Me parece un HORROR esto, y la unica forma de arreglarlo es armar un stack nuevo. Asi que mi Github Action tiene un paso que es Armar un .aws/credentials. Es horrible, pero como el SESSION_TOKEN es temporal, es, a mi parecer, menos malo que tener el access key y security key en los secrets.
Ya sabiendo eso, vamos a empezar!
Lo primero que tenemos que hacer es generar nuestro OICD provider. Que es eso, Uds. se preguntaran? Es lo que vamos a usar para autenticarnos desde Github hacia AWS. La forma que lo hace me parece increible, si quieren leer un poco mas como funciona, pueden leer la documentacion: Configuring OpenID Connect in Amazon Web Services.
public class GithubStack
{
[Output]
public Output<string> GitHubRoleArn { get; private set; } = null!;
public GithubStack() => GenerateOIDCProvider();
private void GenerateOIDCProvider()
{
var oidcProvider = new OpenIdConnectProvider("githubOidcProvider", new OpenIdConnectProviderArgs
{
Url = "https://token.actions.githubusercontent.com",
ClientIdLists = { "sts.amazonaws.com" },
ThumbprintLists = { "6938fd4d98bab03faadb97b34396831e3780aea1" }
});
//Si queremos achicar mas el rango, se puede usar algo asi
//= "repo: username/repo:ref:refs/heads/main"
var role = new Role("githubActionsRole", new RoleArgs
{
AssumeRolePolicy = oidcProvider.Arn.Apply(arn =>
{
var policy = new
{
Version = "2012-10-17",
Statement = new[]
{
new
{
Effect = "Allow",
Principal = new { Federated = arn },
Action = "sts:AssumeRoleWithWebIdentity",
Condition = new
{
StringLike = new Dictionary<string, object>
{
["token.actions.githubusercontent.com:sub"]
= "repo:username/reponame:ref:*",
},
}
}
}
};
return JsonSerializer.Serialize(policy);
})
});
// En algun momento, revisar si se puede achicar esto
var inlinePolicy = new RolePolicy("githubActionsInlinePolicy", new RolePolicyArgs
{
Role = role.Id,
Policy = JsonSerializer.Serialize(new
{
Version = "2012-10-17",
Statement = new[]
{
new {
Effect = "Allow",
Action = new[] {
"*"
},
Resource = "*"
},
}
})
});
GitHubRoleArn = role.Arn;
}
}
Reemplacen su user de Github y el repo donde van a tener la accion. Chequeen que no haya errores, el formato es "repo:username/reponame:ref:*"
.
Con esto, le estamos diciendo que cuando Github Actions pida un STS, se va a autenticar con un rol que tiene permiso para todo. Aca tengo que hacer un parate y decirles "ESTO ESTA MAL"; siempre deberian usar un rol que SOLO tenga los permisos que necesiten y NADA MAS. Pero la realidad es que... cuando la infra genera desde dominios en Route53, certificados, API Gateways, Logs, actualiza lambdas, entre otras cosas, realmente habria que ir agregando CADA UNO DE LOS PERMISOS y darle a eso. El rol que usaba Pulumi tambien tenia *, asi que bueno, en mi caso sabiendo que no es lo ideal, para mi esta bien.
Desde su stack de Pulumi, lo llaman y listo. Una vez generado el role, pueden ir a la consola y lo van a ver en IAM -> Identity providers
Si van a la parte del rol que les genero, van a ver la policy con el *
(que NO deberia estar)
Y en Trust relationships, pueden ver lo que les decia antes del OICD
Ya con eso, GitHub actions va a poder "hablar" con AWS sin la necesidad de usar Access Key y Security Key.
Lo siguiente que debemos hacer es copiar el ARN de nuestro rol, y definirlo como Secret en Github
Nota: en mi caso, yo tengo dos entornos (Sandbox y Produccion), los ejemplos los hago con Sandbox.
Despues, nos logueamos en nuestra cuenta de Pulumi y generamos un Personal Access Token. Guardan ese token y lo agregan como Secret en Github tambien.
Una vez que ya tenemos esos dos datos, solo resta armar la accion. Para eso, creamos un archivo en .github\workflows\deploy-sandbox.yml
, y armamos el workflow. Como les conte antes, como mi stack lo corria desde mi local, si o si necesitaba un profile con ese nombre (increible eso...) pero ese paso no es necesario si no tienen ese problema.
name: Deploy (Sandbox)
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
deploy-sandbox:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS 👤
uses: aws-actions/configure-aws-credentials@v3
with:
role-to-assume: ${{ secrets.AWS_ROLE_SANDBOX_ARN }}
aws-region: us-east-1
- name: Pulumi Login 🔐
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
run: pulumi login
# Esto es porque Pulumi metio en nombre del profile en el state del cloud
# y ahora no tengo manera "facil" de cambiarlo sin re-hacer el stack. Asi que por ahora
# vamos a generar el profile a "pata" y listo. SI NO TIENEN ESE PROBLEMA ESTE PASO LO PUEDEN OBVIAR
- name: Setup AWS Config 🔐
run: |
mkdir -p ~/.aws
echo "[sandbox]" > ~/.aws/credentials
echo "aws_access_key_id=${{ env.AWS_ACCESS_KEY_ID }}" >> ~/.aws/credentials
echo "aws_secret_access_key=${{ env.AWS_SECRET_ACCESS_KEY }}" >> ~/.aws/credentials
echo "aws_session_token=${{ env.AWS_SESSION_TOKEN }}" >> ~/.aws/credentials
chmod 600 ~/.aws/credentials
echo "[profile sandbox]" > ~/.aws/config
echo "region=${{ env.AWS_REGION }}" >> ~/.aws/config
chmod 600 ~/.aws/config
- name: Setup .NET 🌎
uses: actions/setup-dotnet@v3
with: { dotnet-version: '8.x' }
- name: Install Lambda Tools 🌎
run: dotnet tool install -g Amazon.Lambda.Tools
- name: Restore Packages 📦
working-directory: src
run: |
dotnet restore
- name: Build & Package XXX 📦
working-directory: src
run: |
cd Api/
dotnet lambda package \
--configuration Release \
--framework net8.0 \
--output-package output/api.zip
- name: Pulumi Up 🚀
uses: pulumi/actions@v6
with:
command: up
stack-name: dev
work-dir: src/infra
suppress-progress: 'true'
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_PROFILE: sandbox
AWS_REGION: ${{ env.AWS_REGION }}
Ya con todo listo, podemos ejecutarlo desde la parte de acciones:
Esto despues obviamente se puede usar para CI/CD (hacer releases automaticos a produccion cuando se mergea a main
, por ej).
Espero que les sirva y si tienen alguna duda, pueden dejar un comentario
Comentarios Recientes
No hay comentarios, porque no dejás alguno?

Deja un comentario
