---
title: "Assinando documentos do Lacuna Signer no Amazon ECS Fargate com CloudFormation"
description: "Procedimento de implantação do Bulk Signer no Amazon ECS Fargate com RDS for SQL Server, S3 e AWS KMS, com a infraestrutura da AWS criada por uma única stack do CloudFormation, e de assinatura de documentos criados em uma pasta do Lacuna Signer com o método SignerFolder e o webhook Signer:WebhookSecret, com os parâmetros do template, os contratos de interface e os códigos de erro envolvidos."
canonical: "https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation"
locale: "pt-BR"
product: "Bulk Signer"
last_updated: "2026-11-11"
---

# Assinando documentos do Lacuna Signer no Amazon ECS Fargate com CloudFormation

Esta página especifica uma implantação do Bulk Signer em instância única na Amazon Web Services que assina
documentos criados no **Lacuna Signer**. Usuário ou API cria um documento em uma pasta do Signer vinculada a
um perfil de assinatura do Bulk Signer; o Bulk Signer o assina com o certificado da organização, cuja
chave privada está armazenada no **AWS KMS**, e o Signer mantém o documento assinado. A implantação é
composta por:

- a imagem de container do Bulk Signer, executada como **uma** task do Amazon ECS no AWS Fargate, atrás de
  um Application Load Balancer;
- o banco de dados operacional no **Amazon RDS for SQL Server**;
- o compartilhamento de trabalho e o certificado de assinatura em **um bucket do Amazon S3**;
- a chave de assinatura no **AWS KMS**;
- um perfil de assinatura que usa o
  [método `SignerFolder`](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#assinando-em-uma-pasta-do-lacuna-signer--signerfolder);
- o webhook `Signer:WebhookSecret`, pelo qual o Signer notifica o Bulk Signer sobre documentos novos.

A infraestrutura é a especificada em [Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws). Nesta página, um **template do
CloudFormation** cria toda ela em uma única stack ([passo 5](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#stack)), em uma conta AWS vazia. Três itens
ficam fora da stack e são preparados antes dela: a chave de assinatura, importada do PFX para o KMS
([passo 2](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#signing-key)); a imagem de container, copiada para o ECR ([passo 3](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#image)); e os segredos
cujos valores uma pessoa digita ([passo 4](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#secrets)). Depois da stack vêm o registro de DNS e o lado do
Lacuna Signer: o perfil, o webhook e um teste ([passos 6 a 10](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#dns)).

A mesma implantação, montada comando a comando com o AWS CLI em vez de um template, está especificada em
[Amazon ECS Fargate + Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder). As duas páginas produzem os mesmos recursos e a
mesma configuração; esta deixa menos comandos para executar.

Um ambiente já implantado manualmente a partir de [Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws) não é assumido pelo
template. Para incluir nele a integração com o Lacuna Signer, siga o [passo 1](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#prepare-signer), crie os
dois segredos do Lacuna Signer do [passo 4](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#secrets), inclua as configurações `Signer` da
[Configuração do Bulk Signer](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#bulk-signer-configuration) em uma nova revisão da definição de task,
implante-a e siga os [passos 8 a 10](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#profile).

O procedimento foi validado em um ambiente de homologação: um PDF criado no Lacuna Signer foi assinado
pela task do Fargate com um certificado e-CNPJ cuja chave estava armazenada no AWS KMS, em um job com
tempo total de processamento de 1,2 segundo. Em 11/11/2026, o template foi implantado em `sa-east-1` com
os comandos dos passos 5 e 7, em uma estação Windows com o Git Bash: a stack ficou pronta 18 minutos
depois do `create-stack`, todas as verificações do passo 7 passaram na primeira tentativa, uma
atualização com `aws cloudformation deploy` levou três minutos, e a stack foi excluída em seguida.

> **Info: Versão mínima: 2.20.0**
>
> O método `SignerFolder` e a chave `Signer:WebhookSecret` foram introduzidos na 2.16.0. O
> [provider de armazenamento S3](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#the-work-share-in-an-s3-bucket--storageprovider--s3), a
> [origem de certificado `AwsKms`](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#origem--awskms) e a chave
> [`Auth:SessionKeyRing:Placement`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authsessionkeyring--where-the-session-key-ring-is-kept)
> foram introduzidos na 2.20.0. Imagens anteriores recusam a configuração desta página no boot.

> **Perigo: Restrição de instância única**
>
> O [modo cluster](https://docs.lacunasoftware.com/articles/bulk-signer/high-availability) é suportado somente no Azure App Service, e um compartilhamento de
> trabalho no S3 é recusado quando `Cluster:Enabled` está definido. O serviço do ECS que a stack cria é
> configurado para que **duas tasks nunca sejam executadas ao mesmo tempo, inclusive durante uma
> implantação**. Implantações que exigem mais de uma instância devem usar o
> [Azure App Service (modo cluster)](https://docs.lacunasoftware.com/articles/bulk-signer/azure).

## Arquitetura

[![Assinatura em uma pasta do Lacuna Signer na AWS: usuário ou API cria um documento no Signer, o Signer notifica a task do Fargate pelo load balancer, a task assina com a chave no AWS KMS e devolve a assinatura ao Signer](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-architecture-pt.svg)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-architecture-pt.svg)

### Conexões

Em relação a uma implantação que assina somente arquivos, três conexões são acrescentadas. Nenhuma delas
é um recurso da AWS.

| Direção | Conteúdo | Credencial |
|---|---|---|
| Signer → balanceador de carga → task | O webhook: `POST https://<host>/api/signer/notifications` | `Signer:WebhookSecret`, enviado como `Authorization: Bearer` |
| Task → Signer | A varredura, a releitura de cada documento, e o início e a conclusão de cada assinatura | `Signer:ApiKey`, enviada como `X-Api-Key` |
| Task → KMS | `kms:Sign`, uma chamada por documento assinado | O task role |

### Diferenças em relação à assinatura de arquivos

| Aspecto | Comportamento com o método `SignerFolder` |
|---|---|
| Conteúdo do documento | Nenhum arquivo passa pelo Bulk Signer. O Signer envia o hash a assinar, a task o assina no KMS, e o Signer incorpora a assinatura ao documento e o mantém. Nada entra em uma pasta monitorada e nada é gravado em `work/output/`. O documento assinado é baixado no Signer. |
| Identificação do titular | O Signer associa o participante do documento ao CPF contido no certificado do perfil. O certificado deve, portanto, ser um **e-CPF** ou um **e-CNPJ** da ICP-Brasil (que contém o CPF do responsável). |
| Formato da assinatura | O Signer seleciona o formato: PAdES para um PDF, XAdES para XML, CAdES para qualquer outro tipo. A configuração de formato do próprio perfil é ignorada. |

## Função do `Signer:WebhookSecret` no desempenho

### Mecanismos de descoberta

O Bulk Signer tem dois mecanismos para detectar um documento que aguarda a assinatura dele:

| Mecanismo | Funcionamento | Disponibilidade |
|---|---|---|
| Varredura | A cada `Signer:FolderSweepIntervalSeconds` (padrão: 300 segundos), a task lista os documentos pendentes de cada pasta vinculada e relê cada um no Signer. | Sempre ativa. Exige somente `Signer:ApiKey`. |
| Notificação | O Signer chama `POST /api/signer/notifications` quando um documento é criado, assinado ou aprovado. A task enfileira uma checagem para o documento citado na chamada, responde imediatamente e relê esse documento. | Somente quando `Signer:WebhookSecret` está definido. Sem ele, a rota retorna `404`. |

[![Quanto tempo um documento espera: com Signer ele é assinado segundos depois de criado; só com a varredura ele espera a próxima passagem, até cinco minutos por padrão](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-webhook-vs-sweep-pt.svg)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-webhook-vs-sweep-pt.svg)

### Efeito da configuração

| Aspecto | Somente a varredura | Com `Signer:WebhookSecret` |
|---|---|---|
| Tempo até um documento ser detectado | Até o intervalo da varredura: de 0 a 5 minutos por padrão, 2,5 minutos em média. | Segundos. |
| Chamadas à API do Signer | Em cada passagem, uma listagem por pasta vinculada mais uma leitura por documento pendente nela, tenha havido mudança ou não. | Uma leitura por documento notificado. |
| Fluxos com vários participantes | Cada passagem de vez para o titular aguarda uma varredura. | A notificação da etapa anterior à do titular é o evento pelo qual a vez do titular é detectada, em segundos. |

O tempo de detecção também pode ser reduzido, sem o webhook, com um valor menor em
`Signer:FolderSweepIntervalSeconds`. O mínimo é de 60 segundos, e cada passagem repete a mesma listagem e
as mesmas leituras. No mínimo, o número de chamadas ao Signer é cinco vezes o do padrão, e um documento
ainda espera até um minuto. Com o webhook, o número de chamadas é proporcional ao número de eventos, e o
intervalo da varredura pode permanecer no padrão ou ser aumentado.

### Alcance

| Propriedade | Especificação |
|---|---|
| Duração da assinatura | Não é afetada. A configuração altera o tempo até um documento ser detectado, não o tempo necessário para assiná-lo. No ambiente de homologação, um job levou 1,2 segundo, incluída a espera na fila. A vazão é determinada por [`Pipeline:MaxConcurrency`](https://docs.lacunasoftware.com/articles/bulk-signer/configuration) e pela [cota do KMS](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#cota-e-throttling). |
| Garantia de entrega | É dada pela varredura, não pela notificação. Se o Signer não conseguir acessar o balanceador de carga, ou se uma entrega se perder, o documento é assinado na varredura seguinte. Um documento atrasa, nunca se perde. |
| Confiança na notificação | Nenhuma. Uma notificação identifica qual documento ler; nenhum outro conteúdo dela é usado. O documento é relido no Signer com `Signer:ApiKey` antes de um job ser criado. |

### Propriedades de segurança

A rota é pública, e o segredo é o único elemento que distingue uma chamada do Signer de qualquer outra
chamada.

| Propriedade | Especificação |
|---|---|
| Tamanho mínimo | 32 caracteres. Espaço em branco no início ou no fim é recusado no boot. |
| Comparação | Em tempo constante. |
| Chamada sem o segredo | `401`. |
| Efeito de uma chamada forjada que apresente o segredo | No máximo, a task lê um documento no Signer. |

## Pré-requisitos

| Requisito | Especificação |
|---|---|
| Ferramentas | O **AWS CLI v2** autenticado (`aws sts get-caller-identity` precisa responder) e um shell **bash** para os comandos desta página (no Windows, o Git Bash do Git for Windows). O **Docker**, para copiar a imagem (passo 3). **PowerShell 7** e **OpenSSL 3.x** (incluído no Git for Windows), para a importação da chave (passo 2). O **`jq`**, que não vem com o Windows nem com o Git for Windows (`winget install jqlang.jq`; `brew install jq` no macOS; `apt install jq` no Linux), para ler as respostas dos passos 7 a 10 e para enviar a imagem pelo Docker Desktop para Windows. |
| O template | [`bulksigner-ecs-fargate.yaml`](https://docs.lacunasoftware.com/content/bulk-signer/bulksigner-ecs-fargate.yaml), salvo no diretório de trabalho. Ele também está no repositório do produto, em `docs/samples/`. |
| Permissões do IAM | Permissão para criar uma stack do CloudFormation com recursos do IAM nomeados (`CAPABILITY_NAMED_IAM`) e os recursos que ela cria: S3, security groups do EC2, RDS, Secrets Manager, IAM (`iam:CreateRole`, `iam:PutRolePolicy`, `iam:PassRole`), ACM, Elastic Load Balancing, CloudWatch Logs, ECS e Lambda; além de KMS para o passo 2 e ECR para o passo 3. A política `PowerUserAccess`, sozinha, é insuficiente: ela não tem `iam:CreateRole`. |
| Credenciais de registry | As credenciais que a Lacuna emitiu para a imagem de container. Veja [Obtendo o produto](https://docs.lacunasoftware.com/articles/bulk-signer/installation#obtendo-o-produto). |
| Licença | A **licença do PKI SDK**. |
| DNS | Um **hostname** que o Signer consiga acessar pela internet e permissão de escrita na zona de DNS autoritativa para ele. Confirme a delegação com `dig +short NS example.com`: um domínio pode ter uma hosted zone do Route 53 na conta e ainda assim estar delegado a outro provedor. |
| Organização no Lacuna Signer | Uma **organização** em uma instância do Lacuna Signer e um usuário com o papel de administrador da organização. Esse papel é exigido para cadastrar o webhook. |
| Chave de API do Lacuna Signer | Uma **chave de API** dessa organização (item 1.2). |
| Material de assinatura | O **PFX** de um certificado **e-CPF** ou **e-CNPJ** da ICP-Brasil, com chave RSA 2048, 3072 ou 4096, e a senha dele. Um certificado que não contém CPF é recusado na criação do perfil. |
| Identificação do titular | O **CPF** contido no certificado e um **e-mail** do titular. Os documentos devem incluir o titular como participante exatamente com esse CPF. |
| Microsoft Entra ID (opcional) | Somente para o login dos operadores com a conta Microsoft ([item 4.2](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#entra-id)): um tenant, o módulo `Microsoft.Graph.Applications` do PowerShell e uma conta com o papel **Cloud Application Administrator** ou superior. |

### Variáveis de shell

Todos os comandos desta página são executados em uma mesma sessão de shell, inicializada com estas
variáveis:

```bash
export MSYS_NO_PATHCONV=1             # somente no Git Bash; veja a nota abaixo
REGION=sa-east-1
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
NAME=bulksigner                       # o nome da stack e o prefixo de todos os recursos
BUCKET=$NAME-$ACCOUNT                 # criado pela stack; nomes de bucket são globais
HOST=bulksigner.example.com           # o hostname público
SIGNER=https://<endereço do Lacuna Signer>
PROFILE=aws-signer                    # o perfil que assina na pasta do Signer
```

| Variável | Conteúdo |
|---|---|
| `REGION` | A região da AWS de todos os recursos. |
| `ACCOUNT` | O ID da conta da AWS da identidade autenticada. |
| `NAME` | O nome da stack e o prefixo do nome de todos os recursos. De 2 a 21 letras minúsculas, dígitos e hifens. |
| `BUCKET` | O nome do bucket que a stack cria. Inclui o ID da conta porque nomes de bucket do S3 são únicos globalmente. |
| `HOST` | O hostname público da implantação. O Signer faz chamadas a ele. |
| `SIGNER` | O endereço da API do Lacuna Signer (item 1.1), sem barra no final. |
| `PROFILE` | O nome do perfil de assinatura vinculado à pasta do Signer. Não pode ser `default`. |
| `MSYS_NO_PATHCONV` | Desliga a conversão de caminhos do Git Bash. Sem ela, um argumento que começa com `/` é reescrito como um caminho do Windows antes de chegar ao AWS CLI: `aws logs tail /ecs/$NAME` lê um grupo de logs chamado `C:/Program Files/Git/ecs/…`. Em outros shells, a linha não tem efeito. |

### Posicionamento de rede

A stack usa a VPC padrão da região e as sub-redes padrão dela, uma por zona de disponibilidade. A task
recebe um IP público para as chamadas de saída (ECR, KMS, S3, Secrets Manager, Lacuna Signer), e o security
group dela admite tráfego de entrada somente do balanceador de carga. Em sub-redes privadas, a task exige um
**NAT gateway**: o Lacuna Signer não é um serviço da AWS e não pode ser acessado por um VPC endpoint. O
template não oferece sub-redes privadas.

## 1. Preparação do Lacuna Signer

Os quatro itens a seguir são definidos no Lacuna Signer antes da criação de qualquer recurso na AWS.

### 1.1 Endereço da API

`SIGNER` é o endereço em que a API da instância do Signer responde, que não é necessariamente o endereço
da página inicial do produto. Sem uma chave, a API retorna `401`:

```bash
curl -s -o /dev/null -w '%{http_code}\n' $SIGNER/api/documents      # 401
```

### 1.2 Chave de API

Crie uma aplicação na organização e gere a chave dela, conforme especificado no
[passo 1 de Primeiros passos com a API do Signer](https://docs.lacunasoftware.com/articles/signer/get-started#passo-1-crie-a-aplicação-e-a-chave-de-api).

| Propriedade | Especificação |
|---|---|
| Acesso ao menu **Aplicações** | Restrito ao administrador de sistema da instância. Sem esse acesso, solicite uma chave da organização ao administrador da instância do Signer. |
| Formato | `<aplicação>\|<segredo>`. A parte da aplicação pode conter espaços. |
| Exibição | A chave é exibida uma única vez, na criação. |
| Escopo | A organização em que foi criada. O seletor de pastas do passo 8 lista somente as pastas dessa organização, e a task lê os documentos com essa chave. |
| Acesso exigido | Um papel que dê acesso às pastas e aos documentos da organização. |

### 1.3 Pasta

Em **Pastas**, selecione a organização e crie uma pasta com o ícone **Criar pasta**.

| Regra | Especificação |
|---|---|
| Vínculo | De um para um. Dois perfis não podem compartilhar uma pasta. |
| Alcance | A própria pasta. Um documento em uma subpasta dela não é assinado. |

As capturas de tela desta página usam uma pasta chamada `AWSTest`.

### 1.4 Política de assinatura em PDF

Se um PDF assinado com um certificado ICP-Brasil recebe a política da ICP-Brasil (AD-RB) ou uma assinatura
PAdES básica é determinado pela configuração `UseBrazilianPdfSigningPolicies` do Signer, e não pelo Bulk
Signer. A configuração pertence à instância do Signer:

| Instância do Signer | Onde a configuração é definida |
|---|---|
| On-premises | Nas [configurações](https://docs.lacunasoftware.com/articles/signer/on-premises/settings) da instância. |
| Nuvem da Lacuna | Pela Lacuna. Solicite a confirmação do valor em vigor. |

Defina a política antes de o primeiro documento ser assinado.

## 2. A chave de assinatura e o certificado

Um arquivo PFX contém dois itens com destinos diferentes:

| Item | Destino | Propriedade |
|---|---|---|
| Chave privada | AWS KMS | O KMS nunca a devolve. |
| Certificado | Um arquivo `.cer`, que a stack confere com a chave e envia ao bucket S3 | Material público. |

Quando o perfil é aberto, o Bulk Signer lê os dois e recusa o par se as chaves públicas forem diferentes.

[![Importação de um PFX para a stack do CloudFormation: a chave privada é encapsulada na sua estação e importada em uma chave do AWS KMS, o certificado público é entregue à stack, que o confere com a chave e o envia ao S3, e a task do Fargate confere um com o outro](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-key-import-cloudformation-pt.svg)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-key-import-cloudformation-pt.svg)

### 2.1 Importação com o script

O [`Import-PfxToKms.ps1`](https://docs.lacunasoftware.com/articles/bulk-signer/samples#powershell-7--import-pfxtokmsps1) executa a importação completa e
solicita a senha do PFX. O bucket ainda não existe, então o certificado é gravado em um arquivo:

```bash
pwsh ./Import-PfxToKms.ps1 -PfxPath ./signer.pfx -Region $REGION -Alias $NAME \
  -CerPath ./signer.cer -SignerPrincipalArn arn:aws:iam::$ACCOUNT:role/$NAME-task
```

| Parâmetro | Valor | Descrição |
|---|---|---|
| `-PfxPath` | `./signer.pfx` | O arquivo PFX que contém a chave e o certificado. |
| `-Region` | `$REGION` | A região da AWS em que a chave é criada. |
| `-Alias` | `$NAME` | O alias do KMS. Os passos 5 e 8 o citam como `alias/<nome>`. O script é interrompido se o alias já existir. |
| `-CerPath` | `./signer.cer` | O certificado, que o passo 5 entrega à stack. |
| `-SignerPrincipalArn` | O ARN do task role | A entidade de segurança (principal) excluída da negação de `kms:Sign` na política da chave. A stack cria o role, exatamente com esse nome, no passo 5; um ARN pode ser citado antes de o role existir. |

**Política da chave.** A chave é criada com a política padrão do KMS mais uma **negação explícita de
`kms:Sign` para qualquer outra entidade de segurança**. Assim, um administrador da conta não consegue
assinar em nome do titular do certificado sem antes alterar a política da chave, e o CloudTrail registra
essa alteração.

### 2.2 Importação manual

O procedimento a seguir é equivalente ao script e expõe cada chamada. Execute-o em um diretório vazio, em
uma máquina considerada confiável para conter o PFX.

**Importação com o AWS CLI e o OpenSSL**

**1. Extraia o certificado e a chave privada.** Cada comando solicita a senha do PFX. Um PFX protegido
com algoritmos legados exige `-legacy` nos dois comandos.

```bash
openssl pkcs12 -in signer.pfx -clcerts -nokeys | openssl x509 -outform DER -out signer.cer
openssl pkcs12 -in signer.pfx -nocerts -nodes | openssl pkcs8 -topk8 -nocrypt -outform DER -out private-key.der
openssl x509 -inform DER -in signer.cer -noout -text | grep 'Public-Key'       # 2048, 3072 ou 4096
```

O `signer.cer` é material público. O `private-key.der` é a chave privada em texto claro e existe até o
item 7.

**2. Crie uma chave do KMS sem material de chave.** `--origin EXTERNAL` cria uma chave que aguarda uma
importação. A especificação da chave deve corresponder ao tamanho informado no item 1. A política é a
padrão do KMS mais a negação que reserva o `kms:Sign` para o task role:

```bash
cat > key-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    { "Sid": "EnableIamUserPermissions", "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::$ACCOUNT:root" }, "Action": "kms:*", "Resource": "*" },
    { "Sid": "OnlyNamedPrincipalsMaySign", "Effect": "Deny",
      "Principal": "*", "Action": "kms:Sign", "Resource": "*",
      "Condition": { "StringNotEquals": { "aws:PrincipalArn": ["arn:aws:iam::$ACCOUNT:role/$NAME-task"] } } }
  ]
}
EOF
KEY_ID=$(aws kms create-key --region $REGION --origin EXTERNAL --key-spec RSA_2048 --key-usage SIGN_VERIFY \
  --description "Bulk Signer signing key" --policy file://key-policy.json --query KeyMetadata.KeyId --output text)
aws kms create-alias --region $REGION --alias-name alias/$NAME --target-key-id $KEY_ID
```

**3. Obtenha os parâmetros de importação.** O KMS retorna uma chave pública RSA de uso único (a chave de
encapsulamento) e um token de importação, ambos válidos por 24 horas:

```bash
aws kms get-parameters-for-import --region $REGION --key-id $KEY_ID \
  --wrapping-algorithm RSA_AES_KEY_WRAP_SHA_256 --wrapping-key-spec RSA_4096 > import-params.json
jq -r .PublicKey   import-params.json | base64 -d > wrapping-public-key.der
jq -r .ImportToken import-params.json | base64 -d > import-token.bin
```

**4. Encapsule a chave privada.** Uma chave privada RSA excede o tamanho que pode ser cifrado diretamente
com a chave de encapsulamento, então o KMS exige um algoritmo híbrido, `RSA_AES_KEY_WRAP_SHA_256`. A chave
privada é encapsulada com uma chave AES-256 aleatória, a chave AES é cifrada com a chave de
encapsulamento, e o arquivo de importação é a concatenação dos dois, com a chave AES cifrada primeiro.

```bash
openssl rand -out aes-key.bin 32
openssl enc -id-aes256-wrap-pad -K "$(xxd -p -c 64 aes-key.bin)" -iv A65959A6 \
  -in private-key.der -out key-material-wrapped.bin
openssl pkeyutl -encrypt -in aes-key.bin -out aes-key-wrapped.bin -inkey wrapping-public-key.der -keyform DER -pubin \
  -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
cat aes-key-wrapped.bin key-material-wrapped.bin > encrypted-key-material.bin
```

**5. Importe o material de chave.**

```bash
aws kms import-key-material --region $REGION --key-id $KEY_ID \
  --encrypted-key-material fileb://encrypted-key-material.bin --import-token fileb://import-token.bin \
  --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE
```

**6. Compare a chave com o certificado.** As duas linhas de saída devem ser idênticas. É a comparação que
o Bulk Signer faz quando o perfil é aberto:

```bash
aws kms get-public-key --region $REGION --key-id $KEY_ID --query PublicKey --output text | base64 -d \
  | openssl rsa -pubin -inform DER -noout -modulus
openssl x509 -inform DER -in signer.cer -noout -modulus
```

**7. Remova a chave privada.** Guarde o `signer.cer` para o passo 5.

```bash
rm -f private-key.der aes-key.bin key-material-wrapped.bin aes-key-wrapped.bin encrypted-key-material.bin import-token.bin
```

O `rm` remove somente as entradas do diretório. O script sobrescreve cada arquivo com zeros antes de
excluí-lo.

### 2.3 Verificação

```bash
aws kms describe-key --region $REGION --key-id alias/$NAME \
  --query 'KeyMetadata.{State:KeyState,Usage:KeyUsage,Spec:KeySpec,Origin:Origin}'
```

| Item | Valor esperado |
|---|---|
| `State` | `Enabled` |
| `Usage` | `SIGN_VERIFY` |
| `Spec` | `RSA_2048`, `RSA_3072` ou `RSA_4096`, conforme a chave |
| `Origin` | `EXTERNAL` |
| `signer.cer` | Existe no diretório de trabalho. A stack o confere com a chave de novo no passo 5. |

> **Aviso: A chave é tão segura quanto cada cópia do PFX**
>
> O KMS nunca devolve a chave privada, mas a chave existiu fora de um HSM antes da importação. Mantenha
> exatamente **uma** cópia do PFX em backup offline, que é o único meio de importar a chave de novo, e
> exclua todas as demais. Para um certificado **A3** da ICP-Brasil, cuja chave deve permanecer em hardware
> certificado, resolva a questão de conformidade antes de prosseguir.

**Chave gerada no KMS.** Uma chave criada dentro do KMS exige, em vez disso, uma CSR assinada pelo KMS e
um certificado emitido para ela. Esse procedimento está documentado em
[Certificados](https://docs.lacunasoftware.com/articles/bulk-signer/certificates#configuração-na-aws).

## 3. A imagem

Copie a tag que a Lacuna especificou para a instalação para um repositório ECR da conta. A Lacuna informa,
com as credenciais do registry, três valores:

| Variável | Conteúdo |
|---|---|
| `REGISTRY` | Somente o host do registry, por exemplo `<nome>.azurecr.io`. É o argumento do `docker login`, que não aceita um caminho. |
| `REPOSITORY` | O caminho da imagem dentro do registry, por exemplo `lacuna/bulksigner`. |
| `VERSION` | A versão a instalar, por exemplo `2.20.0`. Use uma versão explícita, e não `latest`. |

```bash
REGISTRY=<host do registry da Lacuna>
REPOSITORY=<caminho da imagem no registry>
VERSION=<versão>
IMAGE=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/bulksigner:$VERSION

aws ecr create-repository --repository-name bulksigner --region $REGION \
  --image-tag-mutability IMMUTABLE --image-scanning-configuration scanOnPush=true

docker login $REGISTRY --username <usuário-do-registry>     # solicita o token
docker pull --platform linux/amd64 $REGISTRY/$REPOSITORY:$VERSION
docker tag $REGISTRY/$REPOSITORY:$VERSION $IMAGE
```

Envie a imagem ao ECR. No Linux e no macOS:

```bash
aws ecr get-login-password --region $REGION \
  | docker login --username AWS --password-stdin $ACCOUNT.dkr.ecr.$REGION.amazonaws.com
docker push $IMAGE
```

**No Docker Desktop para Windows**, o `docker login` no ECR falha com
`error storing credentials … The stub received bad data`: o token do ECR excede o tamanho que o
Gerenciador de Credenciais do Windows aceita. Um diretório `--config` vazio não resolve, porque o Docker
Desktop continua usando o Gerenciador de Credenciais. Grave o token diretamente no `config.json` de um
diretório temporário e envie a imagem com ele:

```bash
mkdir -p .docker-ecr
aws ecr get-authorization-token --region $REGION --query 'authorizationData[0].authorizationToken' --output text \
  | jq -R --arg r "$ACCOUNT.dkr.ecr.$REGION.amazonaws.com" '{auths: {($r): {auth: .}}}' > .docker-ecr/config.json
docker --config .docker-ecr push $IMAGE
rm -rf .docker-ecr
```

O diretório contém um token de acesso ao ECR válido por 12 horas; o `rm` o remove. Confirme o envio:

```bash
aws ecr describe-images --region $REGION --repository-name bulksigner --query 'imageDetails[].imageTags' --output text
```

## 4. Segredos informados manualmente

### 4.1 Segredos

A implantação lê seis segredos. A stack gera três deles; os valores dos outros três são emitidos fora da
AWS e são informados manualmente antes da criação da stack. O template recebe somente os ARNs deles, nunca
os valores.

| Segredo | Conteúdo | Criado por | Injetado como |
|---|---|---|---|
| `$NAME/sql-app` | JSON com `username`, `password` e `connectionString` | A stack | `ConnectionStrings__Default` (chave `connectionString`) |
| `$NAME/auth-api-key` | Uma chave aleatória de 48 caracteres: a `X-API-Key` da API REST e a chave de login do dashboard | A stack | `Auth__ApiKey` |
| `$NAME/signer-webhook-secret` | O token que o Signer apresenta em uma notificação: 48 caracteres, sem pontuação | A stack | `Signer__WebhookSecret` |
| `$NAME/pki-sdk-license` | A licença do PKI SDK | Manualmente, abaixo | `Signing__PkiSdkLicense` |
| `$NAME/signer-api-key` | A chave de API do Lacuna Signer do item 1.2, no formato `<aplicação>\|<segredo>` | Manualmente, abaixo | `Signer__ApiKey` |
| `$NAME/entra-client-secret` | O client secret do registro de aplicativo no Microsoft Entra ID (opcional) | Manualmente, item 4.2 | `Auth__EntraId__ClientSecret` |

Crie os dois segredos obrigatórios a partir de um prompt que não ecoa a entrada e guarde os ARNs deles
para o passo 5. Cole a chave de API do Signer exatamente como o Signer a exibiu, incluindo eventuais
espaços na parte da aplicação:

```bash
read -rsp 'Licença do PKI SDK: ' VALUE; echo
PKI_ARN=$(aws secretsmanager create-secret --region $REGION --name $NAME/pki-sdk-license \
  --secret-string "$VALUE" --query ARN --output text)
read -rsp 'Chave de API do Lacuna Signer: ' VALUE; echo
SIGNER_KEY_ARN=$(aws secretsmanager create-secret --region $REGION --name $NAME/signer-api-key \
  --secret-string "$VALUE" --query ARN --output text)
unset VALUE
```

Os segredos também podem ser criados no console (**Other type of secret → Plaintext**, contendo somente o
valor); nesse caso, copie cada ARN para `PKI_ARN` e `SIGNER_KEY_ARN`.

### 4.2 Login pelo Microsoft Entra ID (opcional)

Por padrão, a página de login do dashboard pede a chave `$NAME/auth-api-key`. Com uma seção
`Auth:EntraId`, ela oferece **Entrar com a Microsoft** no lugar, e dois app roles controlam o acesso:
**Administrator** para o dashboard do operador e **Approver** para as páginas do aprovador
([Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#authentraid--login-opcional-pelo-microsoft-entra-id)). O `X-API-Key` da
API REST não muda. Para usar a chave de API no dashboard, pule este item.

> **Aviso: Configure antes da stack**
>
> As sessões criadas pelo login com a chave de API deixam de funcionar quando a seção `Auth:EntraId` é
> incluída. Configurado neste ponto, o Entra ID entra na primeira implantação da stack, e o perfil do passo 8
> já é criado com a conta Microsoft. Para incluí-lo em uma stack em execução, veja [Operação](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#day-two).

**1. Crie o registro de aplicativo.** Atribua ao ambiente um registro dedicado, não compartilhado com
outro ambiente. O [`New-BulkSignerEntraApp.ps1`](https://docs.lacunasoftware.com/articles/bulk-signer/samples#powershell-7--new-bulksignerentraappps1)
cria um registro de tenant único, com a URI de redirecionamento `https://$HOST/signin-oidc`, os app roles
`Administrator` e `Approver`, a claim `email` e um aplicativo empresarial com **Assignment required**:

```bash
pwsh ./New-BulkSignerEntraApp.ps1 -BaseUrl https://$HOST -DisplayName "Lacuna Bulk Signer (AWS)"
```

O script recusa um nome de exibição que já existe no tenant. Ao final, ele exibe uma única vez o bloco
`Auth__EntraId__TenantId`, `Auth__EntraId__ClientId` e `Auth__EntraId__ClientSecret`.

**2. Armazene os valores.** O client secret vai para o Secrets Manager por um prompt que não ecoa a
entrada; o tenant e o client não são segredos:

```bash
read -rsp 'Client secret: ' VALUE; echo
ENTRA_ARN=$(aws secretsmanager create-secret --region $REGION --name $NAME/entra-client-secret \
  --secret-string "$VALUE" --query ARN --output text)
unset VALUE
TENANT_ID=<o valor de Auth__EntraId__TenantId>
CLIENT_ID=<o valor de Auth__EntraId__ClientId>
```

Limpe o console em seguida.

**3. Atribua as pessoas.** No centro de administração do Entra, em **Enterprise applications → o
aplicativo → Users and groups**, atribua o app role **Administrator** a cada operador. Uma conta sem
atribuição não consegue entrar, e uma conta que se autentica sem nenhum dos dois roles é recusada.

| Item | Descrição |
|---|---|
| Dependência dos forwarded headers | O login monta a URI de redirecionamento a partir do esquema que o Bulk Signer vê. A stack define `Hosting:ForwardedHeaders`; sem essa configuração, a URI seria `http://…/signin-oidc`, e a Microsoft a recusaria com `AADSTS50011`. |
| Expiração do client secret | O tempo de vida padrão do script é de doze meses. Um segredo expirado só falha na hora do login, com um erro `AADSTS`. Faça a rotação no mesmo segredo e force uma nova implantação ([Operação](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#day-two)). |

## 5. A stack

### 5.1 O que a stack cria

Uma stack do CloudFormation, chamada `$NAME`, cria toda a infraestrutura da AWS da implantação. Todo nome
de recurso começa com `$NAME`.

| Recurso | Nome | Especificação |
|---|---|---|
| Bucket do S3 | `$NAME-<conta>` | Todo acesso público bloqueado; uploads multipart incompletos cancelados após um dia; a pasta `inputs/default/`; o certificado em `signing-material/$NAME.cer`. |
| Security groups | `$NAME-alb`, `$NAME-task`, `$NAME-db` | TCP 443 e 80 de `0.0.0.0/0` para o balanceador de carga; TCP 8080 do balanceador de carga para a task; TCP 1433 da task para o banco de dados. |
| RDS for SQL Server | `$NAME` | `sqlserver-ex` 16.00 (SQL Server 2022), `db.t3.small`, 20 GiB gp3, criptografado, sem acesso público, backups de sete dias. O usuário mestre `bsadmin` tem uma senha gerenciada pelo RDS no Secrets Manager. |
| Banco de dados e login | `BulkSigner`, `bulksigner` | Criados por uma task avulsa do Fargate que executa o `sqlcmd` (imagem `mcr.microsoft.com/mssql-tools`) como usuário mestre: o banco com `READ_COMMITTED_SNAPSHOT` ligado, e um login com `db_datareader`, `db_datawriter` e `db_ddladmin`, não `db_owner`. A stack falha se a task terminar com um código diferente de `0`. |
| Segredos | `$NAME/sql-app`, `$NAME/auth-api-key`, `$NAME/signer-webhook-secret` | Gerados pelo Secrets Manager (item 4.1). A connection string traz `TrustServerCertificate=True`, porque a imagem do Bulk Signer não confia na autoridade certificadora do Amazon RDS ([passo 5 de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#5-o-banco-de-dados)). |
| Roles do IAM | `$NAME-exec`, `$NAME-task` | `$NAME-exec`, assumido pelo Amazon ECS: `AmazonECSTaskExecutionRolePolicy` e `secretsmanager:GetSecretValue` nos seis segredos e no segredo mestre do RDS (este, somente para a task do banco). `$NAME-task`, assumido pelo Bulk Signer: `kms:DescribeKey`, `kms:GetPublicKey` e `kms:Sign` na chave do passo 2, `s3:ListBucket` no bucket, e leitura, escrita e exclusão dos objetos dele. Os dois confiam em `ecs-tasks.amazonaws.com` somente na conta. |
| Certificado do ACM | — | Para `$HOST`, com validação por DNS (item 5.3). Não é criado quando o ARN de um certificado existente é informado. |
| Balanceador de carga e target group | `$NAME` | Voltado à internet, idle timeout de 300 segundos, campos de cabeçalho inválidos descartados. Listener HTTPS com `ELBSecurityPolicy-TLS13-1-2-2021-06`; listener HTTP que redireciona para HTTPS com `301`. Target group na porta 8080, health check `/api/ready` a cada 30 segundos, deregistration delay de 30 segundos. A finalidade de cada configuração está especificada no [passo 7 de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#7-https). |
| Grupos de logs | `/ecs/$NAME`, `/aws/lambda/$NAME-provisioner` | Retenção de 30 dias. |
| Cluster, definição de task e serviço | `$NAME`, `$NAME`, `bulksigner` | Fargate, 1 vCPU e 2 GB, `X86_64`, porta 8080, `stopTimeout` 60. Uma task, com as configurações de implantação do item 5.5. |
| Função Lambda | `$NAME-provisioner` | Executa as quatro operações para as quais o CloudFormation não tem tipo de recurso: encontra a VPC padrão, grava a pasta `inputs/default/`, confere o certificado com a chave do KMS e o envia ao bucket, e cria o banco de dados. Não fica na VPC e só chama APIs da AWS. |

> **Observação: A porta 443 deve permanecer acessível ao Signer**
>
> O webhook é uma chamada **do Signer para o balanceador de carga**. Se o grupo `$NAME-alb` for restrito a
> endereços de origem específicos, os endereços a partir dos quais a instância do Signer faz chamadas
> também devem ser admitidos; obtenha-os com o operador da instância. Se o Signer não puder ser admitido,
> omita o webhook. A varredura, sozinha, assina todos os documentos, com o atraso especificado em
> [Função do `Signer:WebhookSecret` no desempenho](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#why-webhook-secret).

O bucket usa três prefixos, que não podem ser aninhados:

| Prefixo | Conteúdo | Gravado por | Usado pela pasta do Signer |
|---|---|---|---|
| `signing-material/` | O certificado de assinatura (`.cer`) | A stack, a partir do `signer.cer` do passo 2 | **Sim.** Lido quando o perfil é aberto. |
| `work/` | O compartilhamento de trabalho | O Bulk Signer, no boot | Não. Ainda assim, o Bulk Signer não inicia sem um compartilhamento de trabalho. |
| `inputs/default/` | Uma pasta monitorada, para arquivos assinados pelo perfil `default` | A stack | Não. |

**O versionamento e o Object Lock devem permanecer desabilitados.** As diferenças de comportamento de um
compartilhamento de trabalho no S3 estão documentadas em
[Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#the-work-share-in-an-s3-bucket--storageprovider--s3).

### 5.2 Parâmetros

| Parâmetro | Valor nesta página | Descrição |
|---|---|---|
| `Prefix` | `$NAME` | O prefixo do nome de todos os recursos. O task role é `<Prefix>-task`, o role citado no passo 2. Padrão: `bulksigner`. |
| `HostName` | `$HOST` | O hostname público. |
| `ImageUri` | `$IMAGE` | A imagem no ECR, do passo 3. |
| `KmsKeyAlias` | `alias/$NAME` | O alias do passo 2. Padrão: `alias/bulksigner`. |
| `SigningCertificate` | O base64 de `signer.cer` | O certificado do passo 2, em uma linha. A stack o recusa se a chave pública dele não for a da chave do KMS, antes que outro recurso dependa dele, e o envia ao bucket. |
| `PkiSdkLicenseSecretArn` | `$PKI_ARN` | Item 4.1. |
| `SignerEndpoint` | `$SIGNER` | O endereço da API do Signer, sem barra no final. Vazio para uma implantação que só assina arquivos: sem configurações `Signer__*` e sem segredo do webhook. |
| `SignerApiKeySecretArn` | `$SIGNER_KEY_ARN` | Item 4.1. Obrigatório com `SignerEndpoint`. |
| `CertificateArn` | Vazio | Um certificado emitido do ACM para `$HOST` em `$REGION`. Vazio: a stack solicita um, com validação por DNS (item 5.3). |
| `Route53HostedZoneId` | Vazio | Somente quando a stack solicita o certificado e a zona autoritativa para `$HOST` é uma hosted zone do Route 53 na conta: a stack cria o registro de validação ela mesma. |
| `EntraTenantId`, `EntraClientId`, `EntraClientSecretArn` | `$TENANT_ID`, `$CLIENT_ID`, `$ENTRA_ARN` | Item 4.2, opcional. Os três ou nenhum. |
| `AppEnvironment`, `TrustLacunaTestRoot` | `Production`, `false` | Para um ambiente de homologação que assina com os certificados de teste da Lacuna: `Staging` e `true` ([Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signingtrustlacunatestroot--certificados-de-teste-para-homologação)). A stack recusa `true` com `Production`. |
| `DatabaseEngine`, `DatabaseEngineVersion`, `DatabaseInstanceClass` | `sqlserver-ex`, `16.00`, `db.t3.small` | A edição Express basta para um ambiente de homologação; ambientes de produção exigem `sqlserver-web` ou `sqlserver-se`. |

### 5.3 Criação

```bash
aws cloudformation create-stack --region $REGION --stack-name $NAME \
  --template-body file://bulksigner-ecs-fargate.yaml --capabilities CAPABILITY_NAMED_IAM \
  --parameters ParameterKey=Prefix,ParameterValue=$NAME ParameterKey=HostName,ParameterValue=$HOST \
    ParameterKey=ImageUri,ParameterValue=$IMAGE ParameterKey=KmsKeyAlias,ParameterValue=alias/$NAME \
    ParameterKey=SigningCertificate,ParameterValue=$(openssl base64 -A -in signer.cer) \
    ParameterKey=PkiSdkLicenseSecretArn,ParameterValue=$PKI_ARN \
    ParameterKey=SignerEndpoint,ParameterValue=$SIGNER ParameterKey=SignerApiKeySecretArn,ParameterValue=$SIGNER_KEY_ARN \
    ${TENANT_ID:+ParameterKey=EntraTenantId,ParameterValue=$TENANT_ID ParameterKey=EntraClientId,ParameterValue=$CLIENT_ID ParameterKey=EntraClientSecretArn,ParameterValue=$ENTRA_ARN}
```

A última linha inclui os parâmetros do Entra ID somente quando `TENANT_ID` está definida (item 4.2). A
stack também pode ser criada no console, em **CloudFormation → Create stack → With new resources → Upload a
template file**, com os mesmos parâmetros.

**Validação do certificado.** Quando `CertificateArn` está vazio, a stack aguarda a validação do
certificado do ACM e fica parada até o registro de validação existir. Cerca de um minuto depois do início
da criação, o registro aparece nos eventos da stack:

```bash
aws cloudformation describe-stack-events --region $REGION --stack-name $NAME \
  --query "StackEvents[?LogicalResourceId=='Certificate' && contains(ResourceStatusReason || '','CNAME')].ResourceStatusReason" \
  --output text
```

Crie o registro CNAME retornado (`Name` e `Value`) na zona autoritativa. A stack continua assim que o ACM o
valida, em geral em poucos minutos. Com `Route53HostedZoneId`, este item não é necessário.

### 5.4 Conclusão

Aguarde a stack e depois leia as saídas dela:

```bash
aws cloudformation wait stack-create-complete --region $REGION --stack-name $NAME
aws cloudformation describe-stacks --region $REGION --stack-name $NAME --query 'Stacks[0].Outputs' --output table
```

A criação leva cerca de 20 minutos, a maior parte para a instância do RDS; uma validação de certificado
acrescenta o tempo até o registro existir. O primeiro boot da task aplica
as migrações do banco; a stack termina quando o serviço fica estável.

| Saída | Conteúdo |
|---|---|
| `Url` | `https://$HOST/` |
| `DnsRecord` | O registro CNAME a criar no passo 6. |
| `LoadBalancerDnsName` | O nome DNS do balanceador de carga. |
| `BucketName`, `KmsKeyArn` | O bucket e a chave do KMS, como usados no passo 8. |
| `AuthApiKeySecretArn`, `SignerWebhookSecretArn` | Os segredos gerados lidos nos passos 7, 8 e 9. |
| `WebhookUrl` | A URL cadastrada no Signer no passo 9. |
| `LogGroupName` | `/ecs/$NAME`. |

Se a criação falhar, o CloudFormation a reverte e exclui o que criou, inclusive o bucket. A causa está no
primeiro evento com falha:

```bash
aws cloudformation describe-stack-events --region $REGION --stack-name $NAME \
  --query "StackEvents[?ends_with(ResourceStatus,'FAILED')].[LogicalResourceId,ResourceStatusReason]" --output text
```

Veja a [Referência de erros](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#when-something-refuses). Depois de uma reversão, exclua a stack
(`aws cloudformation delete-stack --region $REGION --stack-name $NAME`) antes de criá-la de novo.

### 5.5 O serviço

| Configuração | Valor | Requisito |
|---|---|---|
| `minimumHealthyPercent`, `maximumPercent` | `0`, `100` | Toda implantação para a task em execução antes de iniciar a nova. |
| Quantidade desejada | `1` | Não pode ser aumentada. Auto scaling não pode ser associado. |
| `deploymentCircuitBreaker` | `enable=true`, `rollback=false` | Uma implantação com falha para, em vez de ser revertida, o que iniciaria uma segunda task. |
| Rebalanceamento entre zonas de disponibilidade | Desligado | O rebalanceamento inicia uma task antes de parar outra. |
| Período de carência do health check | 300 segundos | O primeiro boot aplica as migrações antes de o `/api/ready` responder. |

### 5.6 Configuração do Bulk Signer

A stack grava o ambiente e os segredos do container. Um `__` no nome de uma variável de ambiente
corresponde a `:` na chave de configuração.

**Conexão com o Lacuna Signer.** Um host se conecta a uma organização do Signer, e todo perfil que usa o
método `SignerFolder` usa estas configurações
([Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#signer--conexão-com-o-lacuna-signer)).

| Variável de ambiente | Valor nesta implantação | Obrigatória | Descrição |
|---|---|---|---|
| `Signer__Endpoint` | O parâmetro `SignerEndpoint` | Sim | O endereço da API do Signer. |
| `Signer__ApiKey` | Segredo `$NAME/signer-api-key` | Sim | Enviada como `X-Api-Key` em toda chamada ao Signer: a varredura, a releitura de cada documento, e o início e a conclusão de cada assinatura. Também determina a organização cujas pastas o formulário do perfil lista. |
| `Signer__WebhookSecret` | Segredo `$NAME/signer-webhook-secret` | Não | Habilita `POST /api/signer/notifications` e é o token Bearer que o Signer deve apresentar nela. Sem essa chave, a rota retorna `404` e os documentos são detectados somente pela varredura (veja [Função do `Signer:WebhookSecret` no desempenho](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#why-webhook-secret)). |
| `Signer__FolderSweepIntervalSeconds` | `300` (o padrão) | Não | O intervalo entre listagens de cada pasta vinculada, de 60 a 86400 segundos. |

As seguintes regras valem para as chaves do `Signer`:

- **Exigência conjunta.** Definir qualquer chave em `Signer:`, inclusive somente o segredo do webhook,
  torna `Signer:Endpoint` e `Signer:ApiKey` obrigatórias no boot. O template define todas ou nenhuma.
- **Chaves não usadas por este método.** `Signer:PollIntervalSeconds`, `Signer:TimeoutHours` e
  `Signer:MaxConsecutiveApiFailures` valem somente para o método `LacunaSigner`.

**Demais configurações.** As demais chaves são as da implantação base e estão especificadas na
[Referência de configuração de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#referência-de-configuração). Os pontos a seguir
são específicos desta implantação:

| Configuração | Relevância para a integração com o Lacuna Signer |
|---|---|
| `Signing:Certificate:*` | O seed (carga inicial) do perfil de assinatura `default`, aplicado somente no primeiro boot contra um banco vazio. O perfil `default` não pode usar o método `SignerFolder`; o perfil criado no passo 8 cita a mesma chave e o mesmo `.cer`. Toda credencial é `InstanceRole`, então nenhum perfil armazena segredo. |
| `Hosting:ForwardedHeaders:*` | Com os forwarded headers aplicados, a task observa o endereço do Signer em vez do endereço do balanceador de carga. O limite de requisições (rate limiting) da rota de notificações (`RateLimiting:SignerNotification`, 600 chamadas por minuto) é contado por endereço de origem; sem os forwarded headers, todas as origens compartilham uma única cota ([Configuração](https://docs.lacunasoftware.com/articles/bulk-signer/configuration#hosting)). A stack define o bloco CIDR da VPC como rede conhecida. |
| `Storage:Inputs:0:*` | A pasta monitorada para arquivos. |

## 6. DNS

Aponte o hostname para o balanceador de carga, com o registro CNAME da saída `DnsRecord` ou, se a zona
estiver hospedada no Route 53, com um registro alias. O Signer faz chamadas a esse hostname e valida o
certificado dele; o certificado do ACM atende a esse requisito.

## 7. Verificação

```bash
curl -s https://$HOST/api/ready
KEY=$(aws secretsmanager get-secret-value --region $REGION --secret-id $NAME/auth-api-key --query SecretString --output text)
curl -s -H "X-API-Key: $KEY" https://$HOST/api/ready/details | jq '.checks[] | {name, ok, detail}'
```

Além das verificações especificadas no [passo 9 de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#9-verificação) (`database`,
`input-folder:default`, `storage-bucket:…`, `work-share-owner`, `pki-license`), `/api/ready/details` informa
a verificação `cloudhub`, com `ok: true` e o detalhe `not configured (CloudHub:ApiKey)`, que é o valor
normal nesta implantação, e duas verificações da integração com o Lacuna Signer:

| Verificação | Resultado esperado neste ponto |
|---|---|
| `signer-notifications` | `accepted; none has arrived at this instance since it started` |
| `signer-folder-sweep` | `ok: true`. Depois da primeira passagem: `the last pass at this instance finished without a failure at …` |

Se `signer-notifications` informar `not accepted (Signer:WebhookSecret is not set)`, a stack foi criada
com `SignerEndpoint` vazio.

A rota de notificações pode ser testada sem credencial:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://$HOST/api/signer/notifications
```

| Resposta | Significado |
|---|---|
| `401` (`signer-notification.unauthorized`) | Esperado. A rota está habilitada e exige o token Bearer. |
| `404` (`signer-notification.not-configured`) | Nenhum segredo de webhook está definido. |

Com o Entra ID (item 4.2), siga o primeiro redirecionamento do login sem entrar:

```bash
curl -s -o /dev/null -w '%{redirect_url}\n' https://$HOST/api/auth/entra-login
```

A URL deve apontar para `login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize`, com o ID do
aplicativo e `redirect_uri=https%3A%2F%2F<host>%2Fsignin-oidc`. O esquema `https` é o valor a confirmar.

## 8. O perfil de assinatura

O perfil `default` recebe os uploads que não citam perfil e as pastas monitoradas sem vínculo, e o método
`SignerFolder` não aceita arquivos. A pasta do Signer exige, portanto, **um perfil dedicado**.

### 8.1 Criação

Abra o dashboard em `https://<host>/` e entre: com **Entrar com a Microsoft**, usando uma conta com o app
role **Administrator**, se o item 4.2 foi aplicado; do contrário, com a chave armazenada em
`$NAME/auth-api-key`, copiada para a área de transferência sem ser exibida:

```bash
aws secretsmanager get-secret-value --region $REGION --secret-id $NAME/auth-api-key \
  --query SecretString --output text | tr -d '\r\n' | clip
```

Abra **Perfis de assinatura → Novo perfil** (`/profiles/_new`).

[![A página Novo perfil de assinatura com o nome aws-signer, o método Pasta do Lacuna Signer e uma pasta escolhida no seletor](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-profile-form-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-profile-form-pt.png)

| Campo | Valor |
|---|---|
| **Nome** | `aws-signer` (o valor de `$PROFILE`). O nome não pode ser alterado depois da criação. |
| **Método de assinatura** | **Pasta do Lacuna Signer (este host assina documentos criados no Signer)** |
| **Pasta do Lacuna Signer** | A pasta do item 1.3, localizada pela busca por nome. O seletor lista as pastas da organização a que a chave de API pertence e guarda o ID da pasta. |
| **Formato da assinatura** | Ignorado por este método. O Signer seleciona o formato. |
| **Origem do certificado** | **AwsKms** |
| **Onde está o arquivo?** | **Em um objeto do Amazon S3** |
| **Região da AWS**, **Chave do KMS**, **Modo de credencial da AWS** | `$REGION`, `alias/$NAME`, **InstanceRole** |
| **Bucket do S3**, **Chave do objeto no S3**, **Região do S3**, **Modo de credencial do S3** | `$BUCKET`, `signing-material/$NAME.cer`, `$REGION`, **InstanceRole** |
| **Endpoint compatível com S3** | Vazio. O campo é para um armazenamento compatível com S3, como o MinIO. |
| **Endereçamento do bucket por caminho** | Desligado, pelo mesmo motivo. |

Quando o método é selecionado, o formulário oculta as configurações que o Signer determina (a pasta de
entrada, a verificação, a criptografia e a checagem CNAB240) e as salva desabilitadas.

[![Os campos de certificado do novo perfil: Em um objeto do Amazon S3, a região da AWS, o alias da chave do KMS, InstanceRole, e o bucket, a chave do objeto, a região e o modo de credencial do S3](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-profile-kms-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-profile-kms-pt.png)

Selecione **Criar perfil**. **O certificado é aberto durante o salvamento**: a task lê o `.cer` do S3,
solicita a chave pública ao KMS e compara os dois. O salvamento é recusado, e nenhum perfil é criado, nos
seguintes casos:

| Condição | Causa |
|---|---|
| Chave do KMS inacessível | Um alias incorreto, ou um task role sem `kms:GetPublicKey` na chave. |
| Certificado sem CPF | O certificado não é um e-CPF nem um e-CNPJ. |
| Pasta já vinculada | Outro perfil assina na pasta selecionada. |

Um perfil salvo entra em vigor imediatamente. Nenhum reinício é necessário.

### 8.2 Verificação

```bash
curl -s -H "X-API-Key: $KEY" https://$HOST/api/profiles/$PROFILE | jq
```

A resposta deve informar o método `SignerFolder` e `degraded` = `false`. Na página **Perfis de
assinatura**, a situação do perfil é **pronto**.

### 8.3 Certificado separado para o perfil

Para usar um certificado diferente do certificado do perfil `default`, importe-o com outro alias. O
bucket já existe, então o script pode enviar o certificado ele mesmo, com outra chave de objeto:

```bash
pwsh ./Import-PfxToKms.ps1 -PfxPath ./outro.pfx -Region $REGION -Alias $NAME-other \
  -S3Bucket $BUCKET -S3Key signing-material/$NAME-other.cer \
  -SignerPrincipalArn arn:aws:iam::$ACCOUNT:role/$NAME-task
```

**Antes** de criar o perfil, dê ao task role a nova chave em uma política própria; do contrário, o
salvamento é recusado em `kms:GetPublicKey`. A stack gerencia somente a política `bulksigner-app` do role,
então uma política com outro nome sobrevive às atualizações da stack:

```bash
KEY_ARN=$(aws kms describe-key --region $REGION --key-id alias/$NAME-other --query KeyMetadata.Arn --output text)
aws iam put-role-policy --role-name $NAME-task --policy-name kms-$NAME-other --policy-document \
  "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"kms:DescribeKey\",\"kms:GetPublicKey\",\"kms:Sign\"],\"Resource\":\"$KEY_ARN\"}]}"
```

## 9. O webhook no Lacuna Signer

### 9.1 Cadastro

Copie o segredo para a área de transferência, para que ele não seja exibido. O comando da área de
transferência é `clip` no Windows, `pbcopy` no macOS e `xclip -selection clipboard` no Linux:

```bash
aws secretsmanager get-secret-value --region $REGION --secret-id $NAME/signer-webhook-secret \
  --query SecretString --output text | tr -d '\r\n' | clip
```

No Lacuna Signer, com um administrador da organização:

1. Abra **Organizações** e selecione a organização.
2. Na seção **Integração**, abaixo do card **Webhooks**, selecione o botão **+**. Um novo painel
   **Webhook N** é aberto. Uma organização pode ter vários webhooks; um webhook existente de outro sistema
   não é afetado.
3. Preencha os campos conforme especificado abaixo.
4. Selecione **Salvar**, que permanece desabilitado até o token ser informado.
5. Limpe a área de transferência.

[![A seção Integração de uma organização no Lacuna Signer: um novo webhook com a URL terminada em /api/signer/notifications, o tipo de autenticação Bearer e o campo Token de Autenticação](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-webhook-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-webhook-pt.png)

| Campo | Valor | Requisito |
|---|---|---|
| **URL** | `https://<host>/api/signer/notifications` | O hostname público do passo 6, não o nome DNS do balanceador de carga na AWS. O esquema deve ser `https`: o listener da porta 80 responde com um redirecionamento, e clientes HTTP costumam reenviar um `POST` redirecionado como `GET`, o que não entrega nenhuma notificação. |
| **Tipo de autenticação** | **Bearer** | **X-Api-Key** não deve ser usado. Esse header é a credencial REST do próprio Bulk Signer, e uma notificação que o apresenta é recusada. |
| **Token de Autenticação** | O valor de `$NAME/signer-webhook-secret` | Sem a palavra `Bearer`. O Signer acrescenta o prefixo. |

O conteúdo que o Signer envia e o comportamento dele de autenticação e de novas tentativas estão
especificados em [Webhooks](https://docs.lacunasoftware.com/articles/signer/webhooks).

### 9.2 Correspondência entre o Lacuna Signer e o Bulk Signer

A configuração completa do lado do Signer (passos 1 e 9):

| Item no Lacuna Signer | Local | Item correspondente no Bulk Signer |
|---|---|---|
| A aplicação e a chave de API dela | **Aplicações**, na instância | `Signer:ApiKey` |
| A pasta | **Pastas**, na organização | A **Pasta do Lacuna Signer** do perfil |
| A URL do webhook | **Organizações → Integração** | O hostname público, seguido de `/api/signer/notifications` |
| O token do webhook, do tipo **Bearer** | **Organizações → Integração** | `Signer:WebhookSecret` |
| `UseBrazilianPdfSigningPolicies` | As configurações da instância | Nenhum. A configuração determina sozinha a política das assinaturas em PDF. |

### 9.3 Comportamento das entregas

| Comportamento | Especificação |
|---|---|
| Seleção de eventos | Não disponível. O Signer entrega todo evento da organização a todo webhook da organização. |
| Eventos processados pelo Bulk Signer | `DocumentsCreated`, `DocumentSigned` e `DocumentApproved`. Todo outro evento recebe `204`, para que o Signer não o tente de novo. |
| Novas tentativas | O Signer tenta de novo uma entrega que não recebe uma resposta `2xx`, até dez vezes, ao longo de várias horas ([Retentativas](https://docs.lacunasoftware.com/articles/signer/webhooks#retentativas)). |
| Efeito de um token incorreto | Cada entrega recebe `401`, e o resultado `unauthorized` de `bulksigner_signer_notifications_total` aumenta. |

## 10. Teste de assinatura

### 10.1 Criação do documento

No Lacuna Signer:

1. Selecione **Novo documento**, faça upload de um PDF e selecione a organização e **a pasta vinculada**.
2. Inclua o titular do certificado como participante do tipo **Signatário**, com o nome, o **CPF contido
   no certificado** e o e-mail. O titular pode ser o único participante ou vir depois de outros; a task só
   age quando a ação pendente é a do titular.
3. Selecione **Enviar**.

[![A página Novo documento do Lacuna Signer: a organização, a pasta AWSTest e um participante do tipo Signatário com os campos Nome, CPF e Email](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-new-document-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-new-document-pt.png)

### 10.2 Resultado esperado

Em segundos, a página **Jobs** do dashboard lista um job com o nome do documento, marcado como **Lacuna
Signer**, no perfil `aws-signer`:

[![A página Jobs com o job Doc1, marcado como Lacuna Signer e application/pdf, no perfil aws-signer com status Concluído](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-jobs-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-jobs-pt.png)

O job passa pelos seguintes status:

| Status | Significado |
|---|---|
| **Na fila** | O documento foi detectado e o job foi criado. |
| **Processando** | A task releu o documento, iniciou a assinatura e assinou o hash no KMS. |
| **Verificando** | A assinatura foi entregue ao Signer, que a valida e a incorpora ao documento. |
| **Concluído** | O Signer aceitou a assinatura. |

[![A página do job: tempo total de processamento de 1,2 segundo e a linha do tempo Na fila, Processando, Verificando, Concluído, terminando com Signed at Lacuna Signer](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-job-timeline-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-job-timeline-pt.png)

No Lacuna Signer, a ação do titular está concluída e o documento contém a assinatura. Se o titular era o
último participante, o status do documento na pasta é **Concluído**:

[![A pasta vinculada no Lacuna Signer, listando o documento Doc1 com o status Concluído](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-completed-pt.png)](https://docs.lacunasoftware.com/images/bulk-signer/aws-signer-folder-signer-completed-pt.png)

O histórico de eventos do CloudTrail mostra, em alguns minutos, a chamada `Sign` do task role na chave.

### 10.3 Verificação pela linha de comando

```bash
curl -s -H "X-API-Key: $KEY" "https://$HOST/api/jobs?profile=$PROFILE" | jq
curl -s -H "X-API-Key: $KEY" https://$HOST/api/ready/details | jq -r '.checks[] | select(.name=="signer-notifications") | .detail'
curl -s -H "X-API-Key: $KEY" https://$HOST/api/metrics \
  | grep -E 'bulksigner_signer_(notifications_total|folder_signatures_total|folder_jobs_enqueued_total)'
```

| Evidência | Valor esperado |
|---|---|
| Verificação `signer-notifications` | `accepted; the last arrived at this instance at <hora>` |
| `bulksigner_signer_folder_signatures_total{outcome="signed"}` | Incrementado uma vez por assinatura aceita pelo Signer. |
| `bulksigner_signer_notifications_total` | Conta as entregas, por resultado. |

Um documento assinado somente minutos depois de criado foi detectado pela varredura, o que indica que a
notificação não chegou. Nesse caso, a verificação `signer-notifications` ainda informa que nenhuma
notificação chegou, ou o resultado `unauthorized` está aumentando. Veja [Sintomas](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#symptoms).

## Operação

As operações da implantação base estão especificadas no
[passo 11 de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#11-operação). Nesta implantação, toda mudança na infraestrutura
ou na configuração do container é uma **atualização da stack**, não uma mudança feita no console ou com os
comandos do próprio serviço: um recurso alterado fora da stack é sobrescrito pela atualização seguinte. O
`aws cloudformation deploy` mantém o valor de todo parâmetro que não for citado:

```bash
aws cloudformation deploy --region $REGION --stack-name $NAME --template-file bulksigner-ecs-fargate.yaml \
  --capabilities CAPABILITY_NAMED_IAM --parameter-overrides <Parâmetro>=<valor> ...
```

| Operação | Procedimento | Observações |
|---|---|---|
| Atualização de versão | Copie a nova versão para o ECR (passo 3), pause o pipeline (`POST /api/pipeline/pause`), aguarde até não haver job em `Processing` ou `Verifying`, execute a atualização com `ImageUri=<nova imagem>` e retome (`POST /api/pipeline/resume`). | Uma task interrompida durante uma assinatura obriga o boot seguinte a perguntar ao Signer como o job terminou, e alguns desses jobs terminam `Failed` com `signer-folder.interrupted` ([Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer#quando-a-execução-que-o-assinava-termina)). A varredura é executada com o pipeline pausado: documentos novos viram jobs e aguardam na fila. |
| Rotação de segredo | Armazene o novo valor no segredo e inicie uma nova task: `aws ecs update-service --region $REGION --cluster $NAME --service bulksigner --force-new-deployment`, com o pipeline pausado como na atualização de versão. | Uma task lê os segredos quando inicia. Uma nova implantação não altera a stack. |
| Rotação do segredo do webhook | Armazene um novo valor aleatório em `$NAME/signer-webhook-secret`, inicie uma nova task como acima e depois informe o novo valor no webhook no Signer. | O mesmo valor existe dos dois lados. Entre as duas mudanças, as notificações recebem `401`; o Signer as tenta de novo, e a varredura detecta os documentos restantes. Nenhum documento se perde. |
| Rotação da chave de API do Signer | Crie a nova chave no Signer, armazene-a em `$NAME/signer-api-key`, inicie uma nova task como acima e só então revogue a chave anterior. | Com uma chave revogada, toda varredura falha, e a verificação `signer-folder-sweep` falha após três passagens consecutivas com falha. A verificação não afeta o `/api/ready`, então o balanceador de carga mantém a task. |
| Renovação do certificado | Pause o pipeline. Importe o novo PFX com um novo alias (passo 2) e execute a atualização com `KmsKeyAlias=alias/<novo alias>` e `SigningCertificate=$(openssl base64 -A -in <novo>.cer)`. Depois, na página de cada perfil que usa a chave, defina o novo alias em **Editar certificado** (a chave do objeto no S3 não muda), inicie uma nova task como acima e retome. | A stack mantém uma chave: a atualização substitui `signing-material/$NAME.cer` e passa o task role para a nova chave, então, até os perfis citarem o novo alias e uma task ter reiniciado, eles ficam degradados. Se o novo certificado contiver um **CPF diferente**, os documentos pendentes no Signer para o CPF anterior não têm ação para o novo titular. Cada um é registrado uma vez como `Failed` com `signer-folder.no-holder-action`. Avise os usuários que criam documentos antes da mudança. |
| Mudança de pasta | Salve a nova pasta no painel **Certificado** do perfil. | Vale sem reinício. Os documentos que permanecerem na pasta anterior deixam de ser assinados pelo perfil. |
| Inclusão do Entra ID em uma stack em execução | Execute o item 4.2 e depois a atualização com `EntraTenantId`, `EntraClientId` e `EntraClientSecretArn`, com o pipeline pausado como na atualização de versão. | As sessões criadas pelo login com a chave de API deixam de funcionar. Os perfis existentes não mudam. |
| Desativação | Remova o webhook no Signer e depois desabilite o perfil na página dele ou altere o método dele. | Atualizar a stack com `SignerEndpoint` vazio enquanto um perfil ainda usa o método não impede o boot. O perfil fica degradado, e o motivo cita as duas chaves que faltam. |
| Remoção | `aws cloudformation delete-stack --region $REGION --stack-name $NAME` | Exclui todos os recursos da stack, exceto dois, mantidos de propósito: o **bucket**, com o compartilhamento de trabalho, e um **snapshot final** do banco de dados. Exclua-os à parte quando não forem mais necessários; uma nova stack com o mesmo `Prefix` não pode ser criada enquanto o bucket existir. Não fazem parte da stack, e não são excluídos: a chave do KMS, a imagem do ECR e os segredos do passo 4. |

## Contratos de interface

### Endpoint de notificações

`POST /api/signer/notifications` é chamado pelo Lacuna Signer e autenticado com
`Authorization: Bearer <Signer:WebhookSecret>`. O contrato completo está especificado em
[API REST](https://docs.lacunasoftware.com/articles/bulk-signer/rest-api#notificações-do-signer).

| Resposta | Código | Condição |
|---|---|---|
| `202` | — | Um documento em uma pasta vinculada foi enfileirado para checagem, ou já estava enfileirado. |
| `204` | — | Qualquer outra entrega autenticada: um evento que não pode deixar um documento aguardando assinatura, uma pasta sem vínculo, a pasta de um perfil desabilitado ou um corpo que não pode ser lido. |
| `401` | `signer-notification.unauthorized` | O token Bearer está ausente ou incorreto, ou outro esquema foi usado. |
| `404` | `signer-notification.not-configured` | `Signer:WebhookSecret` não está definido. |
| `429` | — | O limite de requisições da rota (`RateLimiting:SignerNotification`) foi excedido. O Signer tenta a entrega de novo. |
| `503` | `signer-notification.unavailable` | O banco operacional não está utilizável na instância, ou há checagens demais enfileiradas. O Signer tenta a entrega de novo. |

As checagens enfileiradas ficam em memória. Um reinício descarta as que ainda não foram feitas, e a
varredura seguinte detecta os documentos correspondentes.

### Outros endpoints usados nesta página

Requisições autenticadas enviam `X-API-Key: <Auth:ApiKey>`.

| Método | Rota | Autenticação | Usado por |
|---|---|---|---|
| `GET` | `/api/ready`, `/api/ready/details` | Anônimo; chave de API para os detalhes | Verificação (passos 7 e 10). |
| `GET` | `/api/profiles/{name}` | Chave de API | Verificação do perfil (passo 8). |
| `GET` | `/api/jobs?profile={name}` | Chave de API | Teste de assinatura (passo 10). |
| `GET` | `/api/metrics` | Chave de API | Teste de assinatura (passo 10). |
| `POST` | `/api/pipeline/pause`, `/api/pipeline/resume` | Chave de API | Implantação ([Operação](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#day-two)). |
| `GET` | `<Signer__Endpoint>/api/documents` | Nenhuma | Confirma o endereço da API do Signer: `401` sem uma chave (item 1.1). |

### Chamadas à AWS feitas pela task para o perfil

| Serviço | Ação | Recurso | Quando é chamada |
|---|---|---|---|
| Amazon S3 | `s3:GetObject` | `signing-material/<nome>.cer` | Quando o perfil é aberto: durante o salvamento no passo 8 e em cada boot. |
| AWS KMS | `kms:DescribeKey`, `kms:GetPublicKey` | A chave de assinatura | Quando o perfil é aberto. |
| AWS KMS | `kms:Sign` | A chave de assinatura | Uma vez por documento assinado. |

## Referência de erros

### Códigos de erro

| Código | Origem | Significado | Solução |
|---|---|---|---|
| `signer-notification.unauthorized` | Rota de notificações, `401` | O token cadastrado no Signer é diferente do segredo, foi informado com a palavra `Bearer`, ou o tipo de autenticação não é **Bearer**. | Corrija o webhook no Signer (passo 9). |
| `signer-notification.not-configured` | Rota de notificações, `404` | O segredo do webhook não chegou à task. | Verifique a revisão da definição de task que o serviço executa e se o execution role consegue ler o segredo. |
| `signer-folder.no-holder-action` | Job, `Failed` | O documento está na pasta vinculada, mas não tem ação de assinatura para o CPF do certificado: o titular está ausente, foi incluído somente como aprovador ou foi incluído sem o CPF. | Inclua o titular como signatário, com o CPF. A varredura seguinte detecta a nova ação. Não admite nova tentativa. |
| `signer-folder.signature-refused` | Job, `Failed` | O Signer recusou a assinatura. Os resultados da validação ficam registrados no histórico do job; a causa usual é o certificado (revogado, expirado ou que não é o do participante). | Corrija a causa informada pelo Signer e tente o job de novo. |
| `signer-folder.interrupted` | Job, `Failed` | A task foi interrompida durante a assinatura. | Verifique o documento no Signer e tente o job de novo. Uma nova tentativa termina `Canceled` se a ação do titular já estiver concluída. |
| `error storing credentials … The stub received bad data` | Docker Desktop para Windows, no `docker login` do ECR | O Gerenciador de Credenciais do Windows recusa o tamanho do token do ECR. | Envie a imagem com o diretório `.docker-ecr` do passo 3. Um diretório `--config` vazio não resolve. |
| `The public key of SigningCertificate is not the public key of alias/…` | Stack, recurso `SigningMaterial` | O `signer.cer` não é o certificado da chave importada. | Informe o `.cer` que o passo 2 gravou para esse alias. |
| `Alias arn:aws:kms:…:alias/… is not found` | Stack, recurso `SigningMaterial` | `KmsKeyAlias` não cita nenhuma chave na região. | Informe o alias do passo 2, com o prefixo `alias/`. |
| `The database setup task … stopped with exit code …` | Stack, recurso `DatabaseSetup` | Um comando do `sqlcmd` falhou. | O primeiro comando com falha está no log que a mensagem cita: `aws logs tail /ecs/$NAME --region $REGION --log-stream-name-prefix dbsetup`. |
| `… already exists` | Stack, qualquer recurso nomeado | Já existe um recurso com o mesmo nome: uma stack anterior com o mesmo `Prefix`, ou o bucket mantido dela. | Use outro `Prefix` ou exclua o recurso anterior. |
| A stack permanece em `CREATE_IN_PROGRESS` em `Certificate` | Stack, ACM | O registro de validação não existe, ou a zona que o contém não é a autoritativa. | Crie o registro do item 5.3 na zona que a verificação `dig +short NS` indica. |
| `Cannot create a snapshot because the database instance … is not currently in the available state` | Exclusão da stack, recurso `Database` | A stack foi excluída enquanto a instância do RDS ainda estava sendo criada, e o snapshot final exige uma instância disponível. | Aguarde `aws rds wait db-instance-available --region $REGION --db-instance-identifier $NAME` e exclua a stack de novo. |
| `Circuit breaker` no evento de `Service` | Stack, ECS | A task não ficou saudável. | O log da task: `aws logs tail /ecs/$NAME --region $REGION --log-stream-name-prefix bulksigner`. |
| `AADSTS50011` | Microsoft Entra ID, no login | A URI de redirecionamento enviada é `http://…` ou não corresponde ao registro de aplicativo. | Confirme que `HostName` é o hostname que os usuários abrem, e a URI `https://<host>/signin-oidc` do registro (item 4.2). |
| `ThrottlingException`, `KeyUnavailableException` | AWS KMS, registrado no job | A cota de requisições do KMS, compartilhada por todos os chamadores da conta e da região, foi excedida, ou a chave estava indisponível. | Veja [Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#jobs-do-aws-kms-falham-com-throttlingexception-ou-keyunavailableexception). |

Um job que termina `Canceled` não tem código: o documento foi cancelado, recusado ou expirou no Signer,
ou já tinha sido assinado pelo titular, antes de a task assiná-lo. Não é uma falha. Todos os códigos
`signer-folder.*` estão especificados em
[Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#um-job-de-pasta-do-lacuna-signer-termina-failed-ou-canceled).

### Sintomas

| Sintoma | Causa | Solução |
|---|---|---|
| A task para no boot e o log cita `Signer:Endpoint` ou `Signer:ApiKey`. | Uma chave em `Signer:` está definida sem essas duas. Definir somente `Signer__WebhookSecret` é suficiente para exigir as duas. | Não ocorre com a stack, que define todas ou nenhuma. Verifique o ambiente da definição de task. |
| A task para no boot e o log cita `Signer:WebhookSecret`. | O segredo tem menos de 32 caracteres, ou tem espaço em branco no início ou no fim, em geral uma quebra de linha no fim do valor do segredo. | Armazene um novo valor sem espaço em branco em `$NAME/signer-webhook-secret` e inicie uma nova task ([Operação](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#day-two)). |
| `signer-notifications` informa *accepted; none has arrived…* depois que um documento foi criado. | O Signer não acessa o balanceador de carga: a URL cadastrada no Signer está incorreta ou usa `http://`, ou o security group `$NAME-alb` não admite os endereços do Signer. | Corrija a URL (passo 9) ou o security group `$NAME-alb` (item 5.1). |
| Os documentos são assinados, mas minutos depois de criados. | A notificação não chega, e os documentos são detectados pela varredura. | Veja [Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#documentos-do-lacuna-signer-só-são-encontrados-no-intervalo-da-varredura-e-não-em-segundos). |
| O seletor de pastas informa que o Signer está inacessível, ou lista as pastas de outra organização. | `Signer__Endpoint` não é o endereço da API do Signer, ou `Signer__ApiKey` pertence a outra organização. | Corrija `SignerEndpoint` com uma atualização da stack, ou o segredo `$NAME/signer-api-key` e inicie uma nova task ([Operação](https://docs.lacunasoftware.com/articles/bulk-signer/aws-signer-folder-cloudformation#day-two)). |
| **Criar perfil** é recusado com uma mensagem sobre o CPF do certificado. | O certificado não contém CPF. | Use um e-CPF ou e-CNPJ da ICP-Brasil. Veja [Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#um-perfil-de-pasta-do-signer-está-degradado-dizendo-que-o-certificado-não-tem-cpf). |
| **Criar perfil** é recusado com uma mensagem sobre o KMS ou o S3. | Falta ao task role `kms:GetPublicKey` na chave ou `s3:GetObject` no `.cer`, o ARN do task role não está excluído da negação da política da chave, ou o `.cer` não corresponde à chave. | Corrija o role ou a política da chave (passo 2 e item 8.3). Veja [Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#um-perfil-awskms-está-degradado-na-inicialização). |
| A verificação `signer-folder-sweep` falha. | Três passagens consecutivas falharam. A causa usual é uma `Signer:ApiKey` revogada ou incorreta. | Veja [Solução de problemas](https://docs.lacunasoftware.com/articles/bulk-signer/troubleshooting#a-linha-signer-folder-sweep-em-apireadydetails-está-vermelha). |

Os erros da implantação base que não envolvem o Lacuna Signer estão especificados na
[Referência de erros de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#referência-de-erros).

## Limitações

| Não oferecido | Alternativa |
|---|---|
| Um arquivo assinado no S3 | O Signer mantém o documento. Baixe-o no Signer; nada é gravado em `work/output/`. |
| Verificação do resultado pelo Bulk Signer | Nenhuma. A verificação fica desabilitada neste método, porque o Signer valida a assinatura ao incorporá-la ao documento. |
| A direção oposta: um arquivo enviado ao Signer para uma pessoa assinar | O método `LacunaSigner`, que usa as mesmas `Signer:Endpoint` e `Signer:ApiKey`, não exige nenhuma outra mudança na AWS e é selecionado por perfil. Veja [Integração com o Lacuna Signer](https://docs.lacunasoftware.com/articles/bulk-signer/lacuna-signer). |
| Mais de uma instância, verificação do certificado do servidor de banco de dados, métricas no CloudWatch, backups feitos pelo Bulk Signer | As [limitações de Amazon ECS Fargate](https://docs.lacunasoftware.com/articles/bulk-signer/aws#limitações) valem sem alteração. |
