Pular para o conteúdo principal

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.

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, 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.

Restrição de instância única

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​

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

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çãoConteúdoCredencial
Signer → balanceador de carga → taskO webhook: POST https://<host>/api/signer/notificationsSigner:WebhookSecret, enviado como Authorization: Bearer
Task → SignerA varredura, a releitura de cada documento, e o início e a conclusão de cada assinaturaSigner:ApiKey, enviada como X-Api-Key
Task → KMSkms:Sign, uma chamada por documento assinadoO task role

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

AspectoComportamento com o método SignerFolder
Conteúdo do documentoNenhum 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 titularO 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 assinaturaO 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:

MecanismoFuncionamentoDisponibilidade
VarreduraA 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çãoO 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

Efeito da configuração​

AspectoSomente a varreduraCom Signer:WebhookSecret
Tempo até um documento ser detectadoAté o intervalo da varredura: de 0 a 5 minutos por padrão, 2,5 minutos em média.Segundos.
Chamadas à API do SignerEm 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 participantesCada 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​

PropriedadeEspecificação
Duração da assinaturaNã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çãoNenhuma. 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.

PropriedadeEspecificação
Tamanho mínimo32 caracteres. Espaço em branco no início ou no fim é recusado no boot.
ComparaçãoEm tempo constante.
Chamada sem o segredo401.
Efeito de uma chamada forjada que apresente o segredoNo máximo, a task lê um documento no Signer.

Pré-requisitos​

RequisitoEspecificação
FerramentasO 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 templatebulksigner-ecs-fargate.yaml, salvo no diretório de trabalho. Ele também está no repositório do produto, em docs/samples/.
Permissões do IAMPermissã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 registryAs credenciais que a Lacuna emitiu para a imagem de container. Veja Obtendo o produto.
LicençaA licença do PKI SDK.
DNSUm 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 SignerUma 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 SignerUma chave de API dessa organização (item 1.2).
Material de assinaturaO 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 titularO 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ávelConteúdo
REGIONA região da AWS de todos os recursos.
ACCOUNTO ID da conta da AWS da identidade autenticada.
NAMEO nome da stack e o prefixo do nome de todos os recursos. De 2 a 21 letras minúsculas, dígitos e hifens.
BUCKETO nome do bucket que a stack cria. Inclui o ID da conta porque nomes de bucket do S3 são únicos globalmente.
HOSTO hostname público da implantação. O Signer faz chamadas a ele.
SIGNERO endereço da API do Lacuna Signer (item 1.1), sem barra no final.
PROFILEO nome do perfil de assinatura vinculado à pasta do Signer. Não pode ser default.
MSYS_NO_PATHCONVDesliga 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.

PropriedadeEspecificação
Acesso ao menu AplicaçõesRestrito 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çãoA chave é exibida uma única vez, na criação.
EscopoA 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 exigidoUm 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.

RegraEspecificação
VínculoDe um para um. Dois perfis não podem compartilhar uma pasta.
AlcanceA 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 SignerOnde a configuração é definida
On-premisesNas configurações da instância.
Nuvem da LacunaPela 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:

ItemDestinoPropriedade
Chave privadaAWS KMSO KMS nunca a devolve.
CertificadoUm arquivo .cer, que a stack confere com a chave e envia ao bucket S3Material 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

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âmetroValorDescrição
-PfxPath./signer.pfxO arquivo PFX que contém a chave e o certificado.
-Region$REGIONA região da AWS em que a chave é criada.
-Alias$NAMEO alias do KMS. Os passos 5 e 8 o citam como alias/<nome>. O script é interrompido se o alias já existir.
-CerPath./signer.cerO certificado, que o passo 5 entrega à stack.
-SignerPrincipalArnO ARN do task roleA 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}'
ItemValor esperado
StateEnabled
UsageSIGN_VERIFY
SpecRSA_2048, RSA_3072 ou RSA_4096, conforme a chave
OriginEXTERNAL
signer.cerExiste no diretório de trabalho. A stack o confere com a chave de novo no passo 5.
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.

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ávelConteúdo
REGISTRYSomente o host do registry, por exemplo <nome>.azurecr.io. É o argumento do docker login, que não aceita um caminho.
REPOSITORYO caminho da imagem dentro do registry, por exemplo lacuna/bulksigner.
VERSIONA 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.

SegredoConteúdoCriado porInjetado como
$NAME/sql-appJSON com username, password e connectionStringA stackConnectionStrings__Default (chave connectionString)
$NAME/auth-api-keyUma chave aleatória de 48 caracteres: a X-API-Key da API REST e a chave de login do dashboardA stackAuth__ApiKey
$NAME/signer-webhook-secretO token que o Signer apresenta em uma notificação: 48 caracteres, sem pontuaçãoA stackSigner__WebhookSecret
$NAME/pki-sdk-licenseA licença do PKI SDKManualmente, abaixoSigning__PkiSdkLicense
$NAME/signer-api-keyA chave de API do Lacuna Signer do item 1.2, no formato <aplicação>|<segredo>Manualmente, abaixoSigner__ApiKey
$NAME/entra-client-secretO client secret do registro de aplicativo no Microsoft Entra ID (opcional)Manualmente, item 4.2Auth__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.

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.

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.

ItemDescrição
Dependência dos forwarded headersO 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 secretO 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.

RecursoNomeEspecificaçã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-dbTCP 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$NAMEsqlserver-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 loginBulkSigner, bulksignerCriados 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-secretGerados 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$NAMEVoltado à 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-provisionerRetenção de 30 dias.
Cluster, definição de task e serviço$NAME, $NAME, bulksignerFargate, 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-provisionerExecuta 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.
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.

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

PrefixoConteúdoGravado porUsado pela pasta do Signer
signing-material/O certificado de assinatura (.cer)A stack, a partir do signer.cer do passo 2Sim. Lido quando o perfil é aberto.
work/O compartilhamento de trabalhoO Bulk Signer, no bootNão. Ainda assim, o Bulk Signer não inicia sem um compartilhamento de trabalho.
inputs/default/Uma pasta monitorada, para arquivos assinados pelo perfil defaultA stackNã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âmetroValor nesta páginaDescrição
Prefix$NAMEO prefixo do nome de todos os recursos. O task role é <Prefix>-task, o role citado no passo 2. Padrão: bulksigner.
HostName$HOSTO hostname público.
ImageUri$IMAGEA imagem no ECR, do passo 3.
KmsKeyAliasalias/$NAMEO alias do passo 2. Padrão: alias/bulksigner.
SigningCertificateO base64 de signer.cerO 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_ARNItem 4.1.
SignerEndpoint$SIGNERO 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_ARNItem 4.1. Obrigatório com SignerEndpoint.
CertificateArnVazioUm certificado emitido do ACM para $HOST em $REGION. Vazio: a stack solicita um, com validação por DNS (item 5.3).
Route53HostedZoneIdVazioSomente 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_ARNItem 4.2, opcional. Os três ou nenhum.
AppEnvironment, TrustLacunaTestRootProduction, falsePara 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, DatabaseInstanceClasssqlserver-ex, 16.00, db.t3.smallA 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ídaConteúdo
Urlhttps://$HOST/
DnsRecordO registro CNAME a criar no passo 6.
LoadBalancerDnsNameO nome DNS do balanceador de carga.
BucketName, KmsKeyArnO bucket e a chave do KMS, como usados no passo 8.
AuthApiKeySecretArn, SignerWebhookSecretArnOs segredos gerados lidos nos passos 7, 8 e 9.
WebhookUrlA 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çãoValorRequisito
minimumHealthyPercent, maximumPercent0, 100Toda implantação para a task em execução antes de iniciar a nova.
Quantidade desejada1Não pode ser aumentada. Auto scaling não pode ser associado.
deploymentCircuitBreakerenable=true, rollback=falseUma implantação com falha para, em vez de ser revertida, o que iniciaria uma segunda task.
Rebalanceamento entre zonas de disponibilidadeDesligadoO rebalanceamento inicia uma task antes de parar outra.
Período de carência do health check300 segundosO 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 ambienteValor nesta implantaçãoObrigatóriaDescrição
Signer__EndpointO parâmetro SignerEndpointSimO endereço da API do Signer.
Signer__ApiKeySegredo $NAME/signer-api-keySimEnviada 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__WebhookSecretSegredo $NAME/signer-webhook-secretNãoHabilita 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__FolderSweepIntervalSeconds300 (o padrão)NãoO 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. Os pontos a seguir são específicos desta implantação:

ConfiguraçãoRelevâ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çãoResultado esperado neste ponto
signer-notificationsaccepted; none has arrived at this instance since it started
signer-folder-sweepok: 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
RespostaSignificado
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).

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

CampoValor
Nomeaws-signer (o valor de $PROFILE). O nome não pode ser alterado depois da criação.
Método de assinaturaPasta do Lacuna Signer (este host assina documentos criados no Signer)
Pasta do Lacuna SignerA 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 assinaturaIgnorado por este método. O Signer seleciona o formato.
Origem do certificadoAwsKms
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 S3Vazio. O campo é para um armazenamento compatível com S3, como o MinIO.
Endereçamento do bucket por caminhoDesligado, 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

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çãoCausa
Chave do KMS inacessívelUm alias incorreto, ou um task role sem kms:GetPublicKey na chave.
Certificado sem CPFO certificado não é um e-CPF nem um e-CNPJ.
Pasta já vinculadaOutro 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:

  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

CampoValorRequisito
URLhttps://<host>/api/signer/notificationsO 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çãoBearerX-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çãoO valor de $NAME/signer-webhook-secretSem 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 SignerLocalItem correspondente no Bulk Signer
A aplicação e a chave de API delaAplicações, na instânciaSigner:ApiKey
A pastaPastas, na organizaçãoA Pasta do Lacuna Signer do perfil
A URL do webhookOrganizações → IntegraçãoO hostname público, seguido de /api/signer/notifications
O token do webhook, do tipo BearerOrganizações → IntegraçãoSigner:WebhookSecret
UseBrazilianPdfSigningPoliciesAs configurações da instânciaNenhum. A configuração determina sozinha a política das assinaturas em PDF.

9.3 Comportamento das entregas​

ComportamentoEspecificação
Seleção de eventosNão disponível. O Signer entrega todo evento da organização a todo webhook da organização.
Eventos processados pelo Bulk SignerDocumentsCreated, DocumentSigned e DocumentApproved. Todo outro evento recebe 204, para que o Signer não o tente de novo.
Novas tentativasO 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 incorretoCada 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

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

O job passa pelos seguintes status:

StatusSignificado
Na filaO documento foi detectado e o job foi criado.
ProcessandoA task releu o documento, iniciou a assinatura e assinou o hash no KMS.
VerificandoA assinatura foi entregue ao Signer, que a valida e a incorpora ao documento.
ConcluídoO 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

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

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ênciaValor esperado
Verificação signer-notificationsaccepted; 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_totalConta 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çãoProcedimentoObservações
Atualização de versãoCopie 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 segredoArmazene 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 webhookArmazene 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 SignerCrie 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 certificadoPause 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 pastaSalve 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çãoExecute 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çãoRemova 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çãoaws cloudformation delete-stack --region $REGION --stack-name $NAMEExclui 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.

RespostaCódigoCondiçã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.
401signer-notification.unauthorizedO token Bearer está ausente ou incorreto, ou outro esquema foi usado.
404signer-notification.not-configuredSigner:WebhookSecret não está definido.
429—O limite de requisições da rota (RateLimiting:SignerNotification) foi excedido. O Signer tenta a entrega de novo.
503signer-notification.unavailableO 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étodoRotaAutenticaçãoUsado por
GET/api/ready, /api/ready/detailsAnônimo; chave de API para os detalhesVerificação (passos 7 e 10).
GET/api/profiles/{name}Chave de APIVerificação do perfil (passo 8).
GET/api/jobs?profile={name}Chave de APITeste de assinatura (passo 10).
GET/api/metricsChave de APITeste de assinatura (passo 10).
POST/api/pipeline/pause, /api/pipeline/resumeChave de APIImplantação (Operação).
GET<Signer__Endpoint>/api/documentsNenhumaConfirma o endereço da API do Signer: 401 sem uma chave (item 1.1).

Chamadas à AWS feitas pela task para o perfil​

ServiçoAçãoRecursoQuando é chamada
Amazon S3s3:GetObjectsigning-material/<nome>.cerQuando o perfil é aberto: durante o salvamento no passo 8 e em cada boot.
AWS KMSkms:DescribeKey, kms:GetPublicKeyA chave de assinaturaQuando o perfil é aberto.
AWS KMSkms:SignA chave de assinaturaUma vez por documento assinado.

Referência de erros​

Códigos de erro​

CódigoOrigemSignificadoSolução
signer-notification.unauthorizedRota de notificações, 401O 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-configuredRota de notificações, 404O 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-actionJob, FailedO 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-refusedJob, FailedO 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.interruptedJob, FailedA 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 dataDocker Desktop para Windows, no docker login do ECRO 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 SigningMaterialO 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 foundStack, recurso SigningMaterialKmsKeyAlias 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 DatabaseSetupUm 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 existsStack, qualquer recurso nomeadoJá 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 CertificateStack, ACMO 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 stateExclusão da stack, recurso DatabaseA 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 ServiceStack, ECSA task não ficou saudável.O log da task: aws logs tail /ecs/$NAME --region $REGION --log-stream-name-prefix bulksigner.
AADSTS50011Microsoft Entra ID, no loginA 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, KeyUnavailableExceptionAWS KMS, registrado no jobA 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​

SintomaCausaSoluçã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 oferecidoAlternativa
Um arquivo assinado no S3O Signer mantém o documento. Baixe-o no Signer; nada é gravado em work/output/.
Verificação do resultado pelo Bulk SignerNenhuma. 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 assinarO 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 SignerAs limitações de Amazon ECS Fargate valem sem alteração.
Ver em Markdown