# Faça a sua nuvem confiar em um job do Latchkey

> Como um job do Latchkey CI faz login na AWS com um token de identidade de vida curta em vez de uma chave de nuvem guardada: o emissor, o subject que a sua política de confiança compara e o passo que pede o token.

Source: https://latchkey.dev/pt/documentation/cloud-identity

## Summary

- Um passo pede ao runner um token de identidade para um público, e o Latchkey o assina como `https://oidc.latchkey.dev`, então um job pode fazer deploy sem nenhuma chave de nuvem guardada em lugar nenhum.
- A sua política de confiança compara o token pelo seu `sub`, que nomeia o seu workspace, o repositório e a ref, ou o ambiente para o qual um owner ou admin aprovou a execução ou que definiu para não precisar de aprovação.
- Um token emitido para um job de uma execução de ensaio, ou de um pipeline em ensaio, traz o seu `sub` depois de `advisory:`, então uma role escrita para execuções reais nunca confia nele.
- Na AWS, crie um provedor OpenID Connect para `https://oidc.latchkey.dev` com o público `sts.amazonaws.com` e depois faça login com `latchkey/aws-login@1.0.0`.

## Como funciona

Um job de uma execução do Latchkey CI pode pedir um token de identidade: um token de vida curta, assinado pelo Latchkey, que diz a qual workspace, repositório e ref ou ambiente o job pertence. A sua nuvem verifica a assinatura com as chaves públicas que o Latchkey publica e compara as claims do token com a confiança que você escreveu, e quando elas batem entrega ao job credenciais de vida curta só dele. Nenhuma chave de nuvem fica guardada no Latchkey, em um segredo ou no seu arquivo de workflow.

Um passo pede um token para um público: o nome que a sua nuvem espera na claim `aud` do token, como `sts.amazonaws.com` para a AWS. Todas as outras claims vêm do registro do próprio Latchkey sobre o job, a sua execução e o seu repositório, nunca da requisição.

## O emissor

| Item | Valor |
| --- | --- |
| Emissor | `https://oidc.latchkey.dev` |
| Documento de descoberta | `https://oidc.latchkey.dev/.well-known/openid-configuration` |
| Chaves públicas | `https://oidc.latchkey.dev/.well-known/jwks.json` |
| Assinatura | RS256, com a chave que o seu `kid` nomeia |
| Validade | 15 minutos, ou menos quando o timeout do job está mais perto, e nunca menos de 5 minutos |

Escreva o emissor exatamente como acima, sem barra no final. Os dois documentos podem ficar em cache por uma hora. Quando o Latchkey troca a sua chave de assinatura, ele publica a chave nova ao lado da antiga antes de assinar com ela, então uma nuvem que lê as chaves públicas do emissor não precisa de nenhuma mudança do seu lado.

## Quais jobs recebem um token

Um job pode pedir enquanto roda, quando é um job de uma execução do Latchkey CI do seu arquivo de workflow sobre um commit do próprio repositório. Estes jobs não recebem token:

- um job de um pull request de um fork, cujos passos não recebem o meio de pedir;
- um job iniciado com `latchkey run`;
- um job de uma execução que o Latchkey iniciou para verificar uma correção que o autorreparo propôs;
- um job que nomeia um ambiente, até que um owner ou admin aprove a sua execução, a menos que o ambiente não precise de aprovação, e nunca em uma execução em uma branch que o ambiente não aceita.

Um token continua válido até o seu `exp`, no máximo 15 minutos, mesmo depois que o seu job termina. Nenhum token é emitido depois que o job termina.

## O subject

A claim `sub` é o que uma política de confiança compara. Ela tem uma de quatro formas:

| Job | `sub` |
| --- | --- |
| Um job que não nomeia nenhum ambiente | `org:<workspace id>:repo:<owner>/<name>:ref:<ref>` |
| Um job que nomeia um ambiente, aprovado ou sem precisar de aprovação | `org:<workspace id>:repo:<owner>/<name>:environment:<environment>` |
| A primeira, em uma execução de ensaio, em um pipeline em ensaio ou em modo consultivo | `advisory:org:<workspace id>:repo:<owner>/<name>:ref:<ref>` |
| A segunda, em uma execução de ensaio, em um pipeline em ensaio ou em modo consultivo | `advisory:org:<workspace id>:repo:<owner>/<name>:environment:<environment>` |

`<workspace id>` é o id do seu workspace, o mesmo em todo token que os jobs do seu workspace recebem. Copie-o de **Workspace ID** em Settings, Account. Os tokens de todos os workspaces do Latchkey vêm do mesmo emissor, então uma política de confiança sempre nomeia o id do seu workspace no `sub` que compara. `<owner>/<name>` é o nome do repositório como o Latchkey o leu por último do seu host de código, então renomear o repositório muda os seus subjects: atualize as roles que o nomeiam quando o renomear, e remova-as quando o excluir, já que um repositório do seu workspace que pegar o nome depois recebe os mesmos subjects. `<ref>` a ref da execução à qual o job pertence, e `<environment>` o ambiente ao qual o job foi vinculado, como o arquivo de workflow o escreve, mantendo maiúsculas e minúsculas: o da própria perna para um valor da matrix, e o escolhido para um mapa de branches. Nenhuma parte pode conter um `:`, então nenhum subject pode ser lido como outro.

Para ver o subject exato que um job recebe, rode este passo uma vez nele. Ele imprime o `sub` decodificado e o id do workspace, nunca o token, que o log esconde de qualquer forma:

yaml:
```yaml
- name: Show this job's identity
  run: |
    answer=$(curl -sS --noproxy '*' -H "Authorization: Bearer $LATCHKEY_ID_TOKEN_REQUEST_TOKEN" \
      "$LATCHKEY_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com")
    if ! token=$(jq -er .value <<<"$answer" 2>/dev/null); then
      echo "No identity token: ${answer:-no answer}" >&2
      exit 1
    fi
    jq -rR 'split(".")[1] | gsub("-"; "+") | gsub("_"; "/") | @base64d | fromjson | .sub, .organization_id' <<<"$token"
```

Um repositório em modo consultivo roda os seus jobs como um ensaio ao lado do seu CI atual, e um repositório no Latchkey CI roda um pipeline em ensaio da mesma forma enquanto o arquivo do GitHub Actions que ele substitui ainda está no commit. Um token emitido para um job de uma execução de ensaio ou de um pipeline em ensaio traz o prefixo `advisory:`, e todo token emitido enquanto o repositório está em modo consultivo também traz, mesmo para uma execução que começou antes de o repositório passar para o modo consultivo. Todo subject sem `advisory:` começa com `org:`, então uma política de confiança escrita para execuções reais nunca aceita um token com o prefixo `advisory:`. Isso é intencional: nem uma execução de ensaio nem um pipeline em ensaio podem fazer deploy. Uma role pensada também para eles, como uma role somente leitura que um teste usa para ler, lista a forma `advisory:` ao lado da forma que começa com `org:`.

Um job que nomeia um ambiente recebe a forma de ambiente só depois que um owner ou admin aprova a sua execução para esse ambiente, ou sem aprovação quando um owner ou admin definiu o ambiente para não precisar de uma, e, quando um owner ou admin definiu as branches do ambiente em Settings, Environments, só em uma execução em uma delas. A sua claim `ref` continua nomeando a ref da execução. Uma role que confia em `environment:production` aceita, portanto, só jobs que um owner ou admin aprovou para production, ou que production iniciou sem aprovação, em execuções que production aceita. Um ambiente só pode dispensar a aprovação enquanto as suas branches estão definidas, e então essas branches são a sua única proteção. Até que um owner ou admin defina as branches, production aceita toda execução, em qualquer branch, tag ou ref de pull request, então defina-as antes que uma role de deploy confie no ambiente.

Uma aprovação também pode vir por uma chave de API com o escopo `jobs:approve`, em nome do owner ou admin que criou a chave. Um agente que tenha uma chave assim e também possa fazer push em um repositório pode aprovar as execuções das próprias mudanças, sem que nenhuma outra pessoa decida.

## Qual ref um token nomeia

Todo token de identidade nomeia a ref da execução à qual o seu job pertence, em duas claims:

- `ref`, por exemplo `refs/heads/main`, `refs/tags/v1.4.0` ou `refs/pull/42/head`;
- `sub`, que termina em `:ref:<ref>` a menos que o job nomeie um ambiente.

Quando um push e um pull request nomeiam o mesmo commit, eles se juntam em uma execução, e todo job dessa execução traz a ref dessa execução.

### Pull requests de uma branch do mesmo repositório

Na maioria das vezes, os jobs de um pull request trazem a ref da branch, `refs/heads/<branch>`: você faz push da branch, o push inicia a execução do commit e o pull request se junta a essa execução. Abrir um pull request para um commit que já rodou não inicia nada novo; os checks dele são os do push.

Um token nomeia `refs/pull/<number>/head` quando o próprio pull request inicia a execução:

- o seu arquivo de workflow roda em `pull_request` e não em `push`, então o push não corresponde a nada e o pull request inicia a execução; ou
- a atualização do pull request chega ao Latchkey antes que o Latchkey tenha planejado os jobs do commit a partir do push.

Então um job em um pull request pode trazer qualquer uma das formas, dependendo dos gatilhos do seu workflow e de qual evento chega primeiro. A claim `event_class` nomeia o evento para o qual a execução foi planejada, como `branch_push` para um push ou `pr_update` para um pull request. Na execução de um pull request, a claim `base_ref` nomeia a branch que o pull request tem como alvo.

### Execuções agendadas

Uma execução agendada é uma execução própria, no head da branch padrão, e os seus jobs trazem a ref da branch padrão, como `refs/heads/main`.

### Pull requests de um fork

Um job em um pull request de um fork não recebe token de identidade. Só código enviado por push ao seu repositório pode pedir um.

## O que uma política de confiança deve comparar

- **Deploys:** compare o subject exato que faz o deploy. Uma role de deploy para um job que nomeia um ambiente compara `org:<workspace id>:repo:<owner>/<name>:environment:production`; uma para uma branch compara `org:<workspace id>:repo:<owner>/<name>:ref:refs/heads/main`, ou uma tag de release. Um token nomeia `refs/heads/main` só em uma execução de um commit que está em `main`, então uma role ligada a `main` só roda código que está em `main`. Uma role de deploy nunca lista a forma `advisory:`.
- **Nunca compare uma ref de pull request para nada que importe.** `refs/pull/<number>/head` muda a cada número de pull request e não aparece em todo job de pull request.
- **Uma role que qualquer job do repositório pode assumir** (testes que leem um bucket de staging, builds de preview): compare todas as refs do repositório pelo prefixo `org:<workspace id>:repo:<owner>/<name>:ref:`. Esse prefixo não corresponde nem à forma de ambiente nem à forma `advisory:`, então uma role que também deve aceitar jobs que nomeiam um ambiente, ou execuções de ensaio e pipelines em ensaio, lista esses prefixos também.
- **Sempre compare o `sub`, com o id do seu workspace nele.** Os tokens de todos os workspaces do Latchkey vêm do mesmo emissor, então uma confiança que verifica só o público, ou só o nome do repositório, também aceita os jobs de outro workspace.

## As claims

| Claim | O que contém |
| --- | --- |
| `iss` | `https://oidc.latchkey.dev` |
| `aud` | O único público que o passo pediu |
| `sub` | O subject, em uma das quatro formas acima |
| `iat`, `nbf`, `exp` | Quando o token foi emitido, quando passa a valer e quando expira, em segundos |
| `jti` | Um id próprio para cada token |
| `organization_id` | O id do seu workspace |
| `repository` | O repositório como `owner/name`, como o Latchkey o leu por último do seu host de código |
| `repository_id` | O id do repositório no Latchkey |
| `job_id` | O job |
| `run_id`, `attempt` | A execução, e a sua tentativa contando a partir de 1 |
| `ref` | A ref da execução |
| `sha` | O commit ao qual a execução está fixada |
| `pipeline_id`, `step_key` | O pipeline e o passo, que mantêm os seus ids em toda ref e todo commit |
| `base_ref` | A branch base de um pull request ou de um grupo de merge, como `refs/heads/<branch>`; em um push, o `base_ref` do próprio push quando o host de código envia um |
| `actor` | Quem iniciou a execução |
| `event_class` | O evento para o qual a execução foi planejada, como `branch_push` ou `pr_update` |

Uma claim sem valor é deixada de fora em vez de ser enviada vazia, então uma regra que compara um valor vazio nunca corresponde por acidente.

## Pedir um token em um passo

Em um job que pode receber um token, todo passo tem duas variáveis: `LATCHKEY_ID_TOKEN_REQUEST_URL`, um endereço no próprio runner, e `LATCHKEY_ID_TOKEN_REQUEST_TOKEN`, a credencial para pedir a ele. Um job de um pull request de um fork não tem nenhuma das duas; qualquer outro job que não pode receber um token é recusado com uma das palavras abaixo. Acrescente `&audience=` e o público ao endereço, envie a credencial como bearer token, e o runner responde `{"value": "<token>", "job_id": "<job id>"}`:

yaml:
```yaml
- name: Get an identity token for AWS
  run: |
    answer=$(curl -sS --noproxy '*' -H "Authorization: Bearer $LATCHKEY_ID_TOKEN_REQUEST_TOKEN" \
      "$LATCHKEY_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com")
    if ! token=$(jq -er .value <<<"$answer" 2>/dev/null); then
      echo "No identity token: ${answer:-no answer}" >&2
      exit 1
    fi
```

Com o CLI 0.9.0 ou posterior instalado no job (`npm install -g @latchkeydev/cli`), `token=$(latchkey oidc request-token --audience sts.amazonaws.com)` faz a mesma requisição. Ele codifica o público por conta própria, mantém a requisição no runner e sai com exit 1 e o motivo quando não há token; `--out <file>` grava o token em um arquivo que só o dono pode ler, em vez de imprimi-lo.

O público é uma única string de 1 a 255 caracteres ASCII visíveis, sem espaço; codifique em porcentagem um público que contenha `&`, `+`, `#` ou `%`. O runner esconde o token no log do job, e no que o autorreparo lê, antes de o passo recebê-lo. Peça o mesmo público de novo e o runner responde o token que guarda enquanto resta pelo menos um terço da sua validade, e um novo depois disso. `--noproxy '*'` mantém a requisição no runner quando o seu job define um proxy.

Uma requisição sem a credencial recebe um 401 sem corpo, e uma que envia qualquer coisa além do público, um 400 sem corpo. Quando o Latchkey não emite um token, o runner responde um status de erro com `{"refusal": "<word>"}`, depois de tentar de novo por até 25 segundos quando uma nova tentativa pode ajudar:

| Palavra | O que significa |
| --- | --- |
| `job_class_ineligible` | O job não roda um commit do próprio repositório em uma execução do seu arquivo de workflow, como um job iniciado com `latchkey run` ou uma execução de uma correção que o autorreparo propôs |
| `job_not_running` | O job não está rodando |
| `configuration_incomplete` | O Latchkey ainda não está configurado para emitir tokens aqui |
| `claims_unavailable`, `signing_failed`, `ledger_unavailable`, `unexpected`, `mint_unavailable` | O Latchkey não conseguiu emitir o token agora; rode o passo de novo |
| `mint_refused` | O Latchkey recusou a requisição: o público não é um que ele aceita, ou a credencial do próprio job foi recusada, como quando o job já terminou |
| `masker_refused` | Não foi possível esconder o token no log, então ele não foi entregue ao passo |

## AWS

Crie um provedor OpenID Connect para o emissor uma vez em cada conta AWS, com o público `sts.amazonaws.com`. Deixe de fora a thumbprint: a AWS verifica o certificado do emissor com as autoridades certificadoras em que confia.

terminal:
```console
$ aws iam create-open-id-connect-provider \
    --url https://oidc.latchkey.dev \
    --client-id-list sts.amazonaws.com
```

Depois, confie no provedor a partir da role. Das claims que um token do Latchkey traz, a AWS só consegue comparar `sub` e `aud`, então tudo de que uma role depende precisa estar no subject que ela compara. Esta role de deploy aceita só jobs de `acme/api` que nomeiam o ambiente `production`, em execuções que um owner ou admin aprovou ou, quando production não precisa de aprovação, em execuções nas branches dele:

`deploy-trust-policy.json`
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.latchkey.dev"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.latchkey.dev:aud": "sts.amazonaws.com",
          "oidc.latchkey.dev:sub": "org:<workspace id>:repo:acme/api:environment:production"
        }
      }
    }
  ]
}
```

Uma role somente leitura que um teste usa para ler pode aceitar qualquer job do repositório que não nomeia nenhum ambiente, incluindo as execuções de ensaio e os pipelines em ensaio. `StringLike` lê `*` como qualquer sequência de caracteres, e a sua lista corresponde quando qualquer um dos seus valores corresponde:

`read-only-trust-policy.json`
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.latchkey.dev"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.latchkey.dev:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "oidc.latchkey.dev:sub": [
            "org:<workspace id>:repo:acme/api:ref:*",
            "advisory:org:<workspace id>:repo:acme/api:ref:*"
          ]
        }
      }
    }
  ]
}
```

No job, faça login com [latchkey/aws-login](https://latchkey.dev/pt/documentation/steps). Ele pede o token, faz login na role e mantém as credenciais renovadas para todos os passos seguintes:

`.latchkey/workflows/deploy.yml`
```yaml
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: small
    environment: production
    steps:
      - uses: latchkey/aws-login@1.0.0
        with:
          role-to-assume: arn:aws:iam::123456789012:role/deploy
          aws-region: us-east-1
      - run: aws sts get-caller-identity
```

> **Google Cloud e Microsoft Entra**
>
> Os dois podem confiar no mesmo token. Os passos deles chegam a esta página quando o Latchkey tiver comprovado cada um de ponta a ponta.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
