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; - o webhook
Signer:WebhookSecret, pelo qual o Signer notifica o Bulk Signer sobre documentos novos.
A infraestrutura é a especificada em Amazon ECS Fargate. Nesta página, um template do CloudFormation cria toda ela em uma única stack (passo 5), 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); a imagem de container, copiada para o ECR (passo 3); e os segredos cujos valores uma pessoa digita (passo 4). 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).
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. 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 não é assumido pelo
template. Para incluir nele a integração com o Lacuna Signer, siga o passo 1, crie os
dois segredos do Lacuna Signer do passo 4, inclua as configurações Signer da
Configuração do Bulk Signer em uma nova revisão da definição de task,
implante-a e siga os passos 8 a 10.
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.
O método SignerFolder e a chave Signer:WebhookSecret foram introduzidos na 2.16.0. O
provider de armazenamento S3, a
origem de certificado AwsKms e a chave
Auth:SessionKeyRing:Placement
foram introduzidos na 2.20.0. Imagens anteriores recusam a configuração desta página no boot.
O modo cluster é 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).
Arquitetura
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. |
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 e pela cota do KMS. |
| 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, 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. |
| 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): 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:
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:
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.
| 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 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.
2.1 Importação com o script
O Import-PfxToKms.ps1 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:
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.
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:
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:
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.
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.
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:
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.
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
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. |
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.
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. |
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:
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:
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:
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:
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). O X-API-Key da
API REST não muda. Para usar a chave de API no dashboard, pule este item.
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.
1. Crie o registro de aplicativo. Atribua ao ambiente um registro dedicado, não compartilhado com
outro ambiente. O New-BulkSignerEntraApp.ps1
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:
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:
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). |
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). |
| 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. |
| 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. |
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.
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.
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). 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
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:
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:
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:
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. 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).
| 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). |
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, tornaSigner:EndpointeSigner:ApiKeyobrigatórias no boot. O template define todas ou nenhuma. - Chaves não usadas por este método.
Signer:PollIntervalSeconds,Signer:TimeoutHourseSigner:MaxConsecutiveApiFailuresvalem somente para o métodoLacunaSigner.
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. 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). 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
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 (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:
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:
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:
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).
| 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.
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
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:
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:
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:
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:
- Abra Organizações e selecione a organização.
- 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.
- Preencha os campos conforme especificado abaixo.
- Selecione Salvar, que permanece desabilitado até o token ser informado.
- Limpe a área de transferência.
| 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.
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). |
| 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:
- Selecione Novo documento, faça upload de um PDF e selecione a organização e a pasta vinculada.
- 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.
- Selecione Enviar.
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:
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. |
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:
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
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.
Operação
As operações da implantação base estão especificadas no
passo 11 de Amazon ECS Fargate. 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:
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). 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.
| 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). |
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. |
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.
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). |
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. |
| 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). |
| 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. |
| 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. |
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. |
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.
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. |
| 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 valem sem alteração. |






