Pular para o conteúdo principal

Assinando documentos do Lacuna Signer no Amazon ECS Fargate

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. Esta página repete aquele procedimento por inteiro, para que ele possa ser executado em uma conta AWS vazia, e acrescenta a integração com o Lacuna Signer. Em um ambiente já implantado a partir daquela página, aplicam-se somente os seguintes passos: o passo 1, os dois segredos do Lacuna Signer no passo 7, as configurações Signer no passo 9, uma nova implantação e os passos 11 a 13.

A mesma implantação com CloudFormation

Amazon ECS Fargate + Lacuna Signer (CloudFormation) cria a infraestrutura da AWS dos passos 2 e 5 a 9 com uma única stack do CloudFormation, em vez dos comandos desta página. Os recursos e a configuração são os mesmos.

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 ambiente foi recriado somente a partir desta página, em uma estação Windows com o Git Bash, e dois documentos criados no Signer foram assinados pelo webhook, o primeiro em 1,7 segundo.

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 do passo 9 é 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
FerramentasUm shell bash (no Windows, o Git Bash do Git for Windows), o AWS CLI v2 autenticado (aws sts get-caller-identity precisa responder), o Docker para copiar a imagem e 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 a importação da chave no passo 3: PowerShell 7 e OpenSSL 3.x (incluído no Git for Windows).
Permissões do IAMPermissão para criar recursos no ECR, ECS, IAM (iam:CreateRole, iam:PutRolePolicy, iam:PassRole), KMS, RDS, S3, Secrets Manager, Elastic Load Balancing, ACM, security groups do EC2 e CloudWatch Logs. 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 9.3): 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 # prefixo de todos os recursos
BUCKET=$NAME-$ACCOUNT # 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
VPC=$(aws ec2 describe-vpcs --region $REGION --filters Name=is-default,Values=true --query 'Vpcs[0].VpcId' --output text)
SUBNETS=$(aws ec2 describe-subnets --region $REGION --filters Name=vpc-id,Values=$VPC --query 'Subnets[].SubnetId' --output text | tr '\t' ',')
VPC_CIDR=$(aws ec2 describe-vpcs --region $REGION --vpc-ids $VPC --query 'Vpcs[0].CidrBlock' --output text)
VariávelConteúdo
REGIONA região da AWS de todos os recursos.
ACCOUNTO ID da conta da AWS da identidade autenticada.
NAMEO prefixo do nome de todos os recursos.
BUCKETO nome do bucket. 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.
VPC, SUBNETS, VPC_CIDRA VPC padrão, os IDs das sub-redes dela (separados por vírgula) e o bloco CIDR dela.
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: --health-check-path /api/ready vira C:/Program Files/Git/api/ready, a criação do target group no item 8.2 falha, e os comandos seguintes falham com $TG vazio. O mesmo vale para --log-group-name /ecs/$NAME no item 9.1. Em outros shells, a linha não tem efeito.

Posicionamento de rede​

O procedimento usa a VPC padrão e as sub-redes públicas dela. 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.

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 11 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. O bucket​

Crie o bucket, bloqueie todo acesso público, cancele uploads multipart incompletos após um dia e crie a pasta monitorada:

aws s3api create-bucket --bucket $BUCKET --region $REGION --create-bucket-configuration LocationConstraint=$REGION
aws s3api put-public-access-block --bucket $BUCKET --public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-lifecycle-configuration --bucket $BUCKET --lifecycle-configuration \
'{"Rules":[{"ID":"abort-incomplete-multipart","Status":"Enabled","Filter":{"Prefix":""},"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":1}}]}'
aws s3api put-object --bucket $BUCKET --key inputs/default/

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

PrefixoConteúdoCriado porUsado pela pasta do Signer
signing-material/O certificado de assinatura (.cer)A importação, no passo 3Sim. 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 defaultO operador, antes do primeiro boot (o último comando acima)Não.

O Bulk Signer nunca cria uma pasta monitorada. No S3, uma pasta é um objeto marcador de tamanho zero.

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.

3. 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.
CertificadoO bucket S3, como um objeto .cerMaterial 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: a chave privada é encapsulada na sua estação e importada em uma chave do AWS KMS, o certificado público é enviado ao S3, e a task do Fargate confere um com o outro

3.1 Importação com o script​

O Import-PfxToKms.ps1 executa a importação completa e solicita a senha do PFX:

pwsh ./Import-PfxToKms.ps1 -PfxPath ./signer.pfx -Region $REGION -Alias $NAME \
-S3Bucket $BUCKET -S3Key signing-material/$NAME.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 9 e 11 o citam como alias/<nome>. O script é interrompido se o alias já existir.
-S3Bucket$BUCKETO bucket que recebe o certificado.
-S3Keysigning-material/$NAME.cerA chave do objeto do certificado.
-SignerPrincipalArnO ARN do task roleA entidade de segurança (principal) excluída da negação de kms:Sign na política da chave. O role é criado no passo 7; 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.

3.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. Faça upload do certificado e remova a chave privada.

aws s3 cp signer.cer s3://$BUCKET/signing-material/$NAME.cer --region $REGION
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.

3.3 Verificação​

aws kms describe-key --region $REGION --key-id alias/$NAME \
--query 'KeyMetadata.{State:KeyState,Usage:KeyUsage,Spec:KeySpec,Origin:Origin}'
aws s3 ls s3://$BUCKET/signing-material/
ItemValor esperado
StateEnabled
UsageSIGN_VERIFY
SpecRSA_2048, RSA_3072 ou RSA_4096, conforme a chave
OriginEXTERNAL
Listagem do S3Contém $NAME.cer
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.

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

5. Security groups​

Crie três security groups, cada um admitindo tráfego de entrada somente do anterior:

GrupoRegra de entradaOrigem
$NAME-albTCP 443 e TCP 800.0.0.0/0
$NAME-taskTCP 8080$NAME-alb
$NAME-dbTCP 1433$NAME-task
SG_ALB=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC --group-name $NAME-alb \
--description "Public HTTPS into the load balancer" --query GroupId --output text)
SG_TASK=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC --group-name $NAME-task \
--description "Bulk Signer, reachable only from the load balancer" --query GroupId --output text)
SG_DB=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC --group-name $NAME-db \
--description "RDS, reachable only from Bulk Signer" --query GroupId --output text)

aws ec2 authorize-security-group-ingress --region $REGION --group-id $SG_ALB --ip-permissions \
'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0}]' \
'IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=0.0.0.0/0}]'
aws ec2 authorize-security-group-ingress --region $REGION --group-id $SG_TASK \
--ip-permissions "IpProtocol=tcp,FromPort=8080,ToPort=8080,UserIdGroupPairs=[{GroupId=$SG_ALB}]"
aws ec2 authorize-security-group-ingress --region $REGION --group-id $SG_DB \
--ip-permissions "IpProtocol=tcp,FromPort=1433,ToPort=1433,UserIdGroupPairs=[{GroupId=$SG_TASK}]"
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.

6. O banco de dados​

6.1 Instância​

aws rds describe-db-engine-versions --region $REGION --engine sqlserver-ex \
--query "DBEngineVersions[?starts_with(EngineVersion,'16.')].EngineVersion" --output text

aws rds create-db-instance --region $REGION --db-instance-identifier $NAME \
--engine sqlserver-ex --engine-version <uma versão 16.00> --license-model license-included \
--db-instance-class db.t3.small --storage-type gp3 --allocated-storage 20 \
--master-username bsadmin --manage-master-user-password \
--vpc-security-group-ids $SG_DB --no-publicly-accessible \
--backup-retention-period 7 --storage-encrypted
aws rds wait db-instance-available --region $REGION --db-instance-identifier $NAME
DB_HOST=$(aws rds describe-db-instances --region $REGION --db-instance-identifier $NAME \
--query 'DBInstances[0].Endpoint.Address' --output text)

As versões 16.00 do engine são o SQL Server 2022. A edição Express é suficiente para um ambiente de homologação; ambientes de produção exigem sqlserver-web ou sqlserver-se.

6.2 Segredo do login da aplicação​

Gere a senha do login da aplicação diretamente em um segredo que também contém a connection string completa:

APP_PW=$(aws secretsmanager get-random-password --region $REGION --password-length 40 \
--exclude-punctuation --require-each-included-type --query RandomPassword --output text)
CS="Server=tcp:$DB_HOST,1433;Initial Catalog=BulkSigner;User ID=bulksigner;Password=$APP_PW;Encrypt=True;TrustServerCertificate=True;"
aws secretsmanager create-secret --region $REGION --name $NAME/sql-app \
--secret-string "$(jq -n --arg p "$APP_PW" --arg c "$CS" '{username:"bulksigner",password:$p,connectionString:$c}')"
unset APP_PW CS

A connection string inclui TrustServerCertificate=True porque a imagem do Bulk Signer não confia na autoridade certificadora do Amazon RDS; essa limitação está especificada no passo 5 de Amazon ECS Fargate.

O banco de dados BulkSigner e o login bulksigner ainda não existem. Eles são criados por uma task avulsa no item 9.2, que depende do execution role do passo 7 e do cluster do item 9.1.

7. Segredos e roles​

7.1 Segredos​

SegredoConteúdoCriadoInjetado como
$NAME/sql-appJSON com username, password e connectionStringItem 6.2ConnectionStrings__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 dashboardAbaixo, geradoAuth__ApiKey
$NAME/pki-sdk-licenseA licença do PKI SDKManualmenteSigning__PkiSdkLicense
$NAME/signer-api-keyA chave de API do Lacuna Signer do item 1.2, no formato <aplicação>|<segredo>ManualmenteSigner__ApiKey
$NAME/signer-webhook-secretO token que o Signer apresenta em uma notificaçãoAbaixo, geradoSigner__WebhookSecret
$NAME/entra-client-secretO client secret do registro de aplicativo no Microsoft Entra ID (opcional)Item 9.3Auth__EntraId__ClientSecret

$NAME/signer-api-key e $NAME/signer-webhook-secret são específicos da integração com o Lacuna Signer.

Gere os dois valores aleatórios diretamente no Secrets Manager, para que não sejam exibidos nem registrados no histórico do shell:

aws secretsmanager create-secret --region $REGION --name $NAME/auth-api-key --secret-string \
"$(aws secretsmanager get-random-password --region $REGION --password-length 48 --exclude-punctuation --query RandomPassword --output text)"
aws secretsmanager create-secret --region $REGION --name $NAME/signer-webhook-secret --secret-string \
"$(aws secretsmanager get-random-password --region $REGION --password-length 48 --exclude-punctuation --require-each-included-type --query RandomPassword --output text)"
Requisito do segredo do webhookMotivo
Pelo menos 32 caracteresUm valor menor é recusado no boot.
Sem espaço em branco no início ou no fimRecusado no boot.
Sem pontuaçãoNão é exigido pelo Bulk Signer. Evita erros de transcrição no campo de token do Signer.

Informe manualmente os dois valores emitidos externamente (a licença do PKI SDK e a chave de API do Signer), no console (Other type of secret → Plaintext, contendo somente o valor) ou a partir de um prompt que não ecoe a entrada. Armazene a chave de API do Signer exatamente como o Signer a exibiu, incluindo eventuais espaços na parte da aplicação.

7.2 Roles​

RoleAssumido porPermissões
$NAME-execO Amazon ECSA política gerenciada da AWS AmazonECSTaskExecutionRolePolicy, mais secretsmanager:GetSecretValue em secret:$NAME/* e no segredo mestre gerenciado pelo RDS (este último somente para a task do item 9.2). Esse role lê os segredos do item 7.1 para o container.
$NAME-taskO Bulk Signerkms:DescribeKey, kms:GetPublicKey e kms:Sign na chave do passo 3, s3:ListBucket no bucket e leitura, gravação e exclusão de objetos no bucket.

Os dois roles usam a mesma política de confiança, que restringe ecs-tasks.amazonaws.com à conta:

cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ecs-tasks.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "$ACCOUNT" },
"ArnLike": { "aws:SourceArn": "arn:aws:ecs:$REGION:$ACCOUNT:*" }
}
}]
}
EOF
aws iam create-role --role-name $NAME-exec --assume-role-policy-document file://trust-policy.json --query Role.Arn
aws iam create-role --role-name $NAME-task --assume-role-policy-document file://trust-policy.json --query Role.Arn

O execution role:

RDS_SECRET=$(aws rds describe-db-instances --region $REGION --db-instance-identifier $NAME \
--query 'DBInstances[0].MasterUserSecret.SecretArn' --output text)
cat > exec-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": ["arn:aws:secretsmanager:$REGION:$ACCOUNT:secret:$NAME/*", "$RDS_SECRET"]
}]
}
EOF
aws iam attach-role-policy --role-name $NAME-exec \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
aws iam put-role-policy --role-name $NAME-exec --policy-name read-deployment-secrets \
--policy-document file://exec-policy.json

O task role:

KEY_ID=$(aws kms describe-key --region $REGION --key-id alias/$NAME --query KeyMetadata.KeyId --output text)
cat > task-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["kms:DescribeKey", "kms:GetPublicKey", "kms:Sign"],
"Resource": "arn:aws:kms:$REGION:$ACCOUNT:key/$KEY_ID" },
{ "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::$BUCKET" },
{ "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
"Resource": "arn:aws:s3:::$BUCKET/*" }
]
}
EOF
aws iam put-role-policy --role-name $NAME-task --policy-name bulksigner-app --policy-document file://task-policy.json

A integração com o Lacuna Signer não exige nenhuma permissão adicional do IAM. O Signer é acessado pela internet com a chave de API, e a assinatura usa a mesma ação kms:Sign de uma assinatura de arquivo.

8. HTTPS​

8.1 Certificado​

CERT=$(aws acm request-certificate --region $REGION --domain-name $HOST --validation-method DNS \
--query CertificateArn --output text)
aws acm describe-certificate --region $REGION --certificate-arn $CERT \
--query 'Certificate.DomainValidationOptions[0].ResourceRecord'

Crie o registro CNAME retornado na zona autoritativa e aguarde a validação:

aws acm wait certificate-validated --region $REGION --certificate-arn $CERT

8.2 Balanceador de carga, target group e listeners​

LB=$(aws elbv2 create-load-balancer --region $REGION --name $NAME --type application --scheme internet-facing \
--subnets ${SUBNETS//,/ } --security-groups $SG_ALB --query 'LoadBalancers[0].LoadBalancerArn' --output text)
aws elbv2 modify-load-balancer-attributes --region $REGION --load-balancer-arn $LB \
--attributes Key=idle_timeout.timeout_seconds,Value=300 Key=routing.http.drop_invalid_header_fields.enabled,Value=true
TG=$(aws elbv2 create-target-group --region $REGION --name $NAME --protocol HTTP --port 8080 --vpc-id $VPC \
--target-type ip --health-check-path /api/ready --health-check-interval-seconds 30 \
--healthy-threshold-count 2 --unhealthy-threshold-count 3 --matcher HttpCode=200 \
--query 'TargetGroups[0].TargetGroupArn' --output text)
aws elbv2 modify-target-group-attributes --region $REGION --target-group-arn $TG \
--attributes Key=deregistration_delay.timeout_seconds,Value=30
aws elbv2 create-listener --region $REGION --load-balancer-arn $LB --protocol HTTPS --port 443 \
--certificates CertificateArn=$CERT --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
--default-actions Type=forward,TargetGroupArn=$TG
aws elbv2 create-listener --region $REGION --load-balancer-arn $LB --protocol HTTP --port 80 \
--default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'

A finalidade de cada configuração está especificada no passo 7 de Amazon ECS Fargate.

8.3 DNS​

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

9. O serviço​

9.1 Grupo de logs e cluster​

aws logs create-log-group --region $REGION --log-group-name /ecs/$NAME
aws logs put-retention-policy --region $REGION --log-group-name /ecs/$NAME --retention-in-days 30
aws ecs create-cluster --region $REGION --cluster-name $NAME --capacity-providers FARGATE

9.2 Criação do banco de dados​

O Bulk Signer cria as tabelas dele, mas nunca o banco de dados, e a instância do RDS é privada. A configuração é executada como uma task avulsa do Fargate na mesma VPC, com o sqlcmd da imagem mcr.microsoft.com/mssql-tools, usando o usuário mestre. Grave o script:

cat > dbsetup.sh <<'EOF'
#!/bin/sh
set -e
SQL=/opt/mssql-tools/bin/sqlcmd
run() { "$SQL" -S "tcp:$DB_HOST,1433" -U "$MASTER_USER" -P "$MASTER_PASSWORD" -N -C -b "$@"; }
run -d master -Q "IF DB_ID('BulkSigner') IS NULL CREATE DATABASE [BulkSigner];"
run -d master -Q "ALTER DATABASE [BulkSigner] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;"
run -d master -Q "IF SUSER_ID('bulksigner') IS NULL CREATE LOGIN [bulksigner] WITH PASSWORD = '$APP_PASSWORD', CHECK_POLICY = ON;"
run -d BulkSigner -Q "IF USER_ID('bulksigner') IS NULL CREATE USER [bulksigner] FOR LOGIN [bulksigner]; ALTER ROLE db_datareader ADD MEMBER [bulksigner]; ALTER ROLE db_datawriter ADD MEMBER [bulksigner]; ALTER ROLE db_ddladmin ADD MEMBER [bulksigner];"
EOF

A imagem não contém o script. A definição de task a seguir o entrega ao container codificado em base64, na linha de comando, e lê as duas senhas do Secrets Manager:

SQL_APP_ARN=$(aws secretsmanager describe-secret --region $REGION --secret-id $NAME/sql-app --query ARN --output text)
jq -n --arg family "$NAME-dbsetup" --arg exec "arn:aws:iam::$ACCOUNT:role/$NAME-exec" \
--arg cmd "echo $(base64 -w0 dbsetup.sh) | base64 -d | sh" --arg db "$DB_HOST" \
--arg master "$RDS_SECRET:password::" --arg app "$SQL_APP_ARN:password::" \
--arg logs "/ecs/$NAME" --arg region "$REGION" '{
family: $family, networkMode: "awsvpc", requiresCompatibilities: ["FARGATE"], cpu: "256", memory: "512",
executionRoleArn: $exec,
containerDefinitions: [{
name: "sqlcmd", image: "mcr.microsoft.com/mssql-tools", essential: true,
entryPoint: ["sh", "-c"], command: [$cmd],
environment: [{name: "DB_HOST", value: $db}, {name: "MASTER_USER", value: "bsadmin"}],
secrets: [{name: "MASTER_PASSWORD", valueFrom: $master}, {name: "APP_PASSWORD", valueFrom: $app}],
logConfiguration: {logDriver: "awslogs",
options: {"awslogs-group": $logs, "awslogs-region": $region, "awslogs-stream-prefix": "dbsetup"}}
}]}' > dbsetup-taskdef.json
aws ecs register-task-definition --region $REGION --cli-input-json file://dbsetup-taskdef.json \
--query taskDefinition.taskDefinitionArn

Execute a task uma vez, aguarde o fim e leia o código de saída:

SETUP_TASK=$(aws ecs run-task --region $REGION --cluster $NAME --task-definition $NAME-dbsetup --launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG_TASK],assignPublicIp=ENABLED}" \
--query 'tasks[0].taskArn' --output text)
aws ecs wait tasks-stopped --region $REGION --cluster $NAME --tasks $SETUP_TASK
aws ecs describe-tasks --region $REGION --cluster $NAME --tasks $SETUP_TASK \
--query 'tasks[0].{exit:containers[0].exitCode,reason:stoppedReason}'
ResultadoSignificado
exit = 0O banco BulkSigner existe com READ_COMMITTED_SNAPSHOT ligado (o RDS, como qualquer SQL Server fora do Azure, não o liga por padrão), e o login bulksigner tem db_datareader, db_datawriter e db_ddladmin, e não db_owner. O log fica vazio: o sqlcmd não escreve nada quando um comando é bem-sucedido.
exit diferente de 0O primeiro comando que falhou está no log: aws logs tail /ecs/$NAME --region $REGION --log-stream-name-prefix dbsetup.
exit vazio e reason com CannotPullContainerError ou ResourceInitializationErrorA task não iniciou: a imagem não foi baixada ou um segredo não pôde ser lido pelo execution role (item 7.2).

O script pode ser executado de novo sem efeito. Não o execute com o serviço em funcionamento: o ROLLBACK IMMEDIATE encerra as transações abertas no banco.

9.3 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 primeira implantação

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 definição de task (item 9.4), e o perfil do passo 11 já é criado com a conta Microsoft. Para incluí-lo em um ambiente já implantado, registre uma nova revisão da definição de task e faça uma implantação (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: ' ENTRA_SECRET; echo
aws secretsmanager create-secret --region $REGION --name $NAME/entra-client-secret \
--secret-string "$ENTRA_SECRET" --query ARN --output text
unset ENTRA_SECRET
TENANT_ID=<o valor de Auth__EntraId__TenantId>
CLIENT_ID=<o valor de Auth__EntraId__ClientId>

Limpe o console em seguida. O execution role já lê secret:$NAME/*.

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ê. As configurações Hosting:ForwardedHeaders do item 9.4 são obrigatórias; sem elas, a URI é http://…/signin-oidc, e a Microsoft a recusa 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.

9.4 Definição de task​

ParâmetroValor
cpu, memory1024, 2048
networkModeawsvpc
runtimePlatformX86_64, LINUX
executionRoleArn, taskRoleArn$NAME-exec, $NAME-task
Containerbulksigner, com a imagem $IMAGE (passo 4), a porta 8080 e stopTimeout de 60
Logsawslogs, grupo /ecs/$NAME

O comando a seguir monta a definição completa, com o ambiente e os segredos do container, e a registra. As entradas Auth__EntraId__* são incluídas somente quando TENANT_ID está definida (item 9.3):

arn() { aws secretsmanager describe-secret --region $REGION --secret-id "$1" --query ARN --output text; }
jq -n --arg name "$NAME" --arg account "$ACCOUNT" --arg region "$REGION" --arg image "$IMAGE" \
--arg bucket "$BUCKET" --arg cidr "$VPC_CIDR" --arg signer "$SIGNER" \
--arg sql "$(arn $NAME/sql-app)" --arg auth "$(arn $NAME/auth-api-key)" --arg pki "$(arn $NAME/pki-sdk-license)" \
--arg sak "$(arn $NAME/signer-api-key)" --arg webhook "$(arn $NAME/signer-webhook-secret)" \
--arg tenant "${TENANT_ID:-}" --arg client "${CLIENT_ID:-}" \
--arg entra "$([ -n "${TENANT_ID:-}" ] && arn $NAME/entra-client-secret)" '{
family: $name, networkMode: "awsvpc", requiresCompatibilities: ["FARGATE"], cpu: "1024", memory: "2048",
runtimePlatform: {cpuArchitecture: "X86_64", operatingSystemFamily: "LINUX"},
executionRoleArn: "arn:aws:iam::\($account):role/\($name)-exec",
taskRoleArn: "arn:aws:iam::\($account):role/\($name)-task",
containerDefinitions: [{
name: "bulksigner", image: $image, essential: true, stopTimeout: 60,
portMappings: [{containerPort: 8080, protocol: "tcp"}],
logConfiguration: {logDriver: "awslogs",
options: {"awslogs-group": "/ecs/\($name)", "awslogs-region": $region, "awslogs-stream-prefix": "bulksigner"}},
environment: ([
{name: "ASPNETCORE_ENVIRONMENT", value: "Production"},
{name: "Database__Provider", value: "SqlServer"},
{name: "Auth__SessionKeyRing__Placement", value: "OperationalStore"},
{name: "Hosting__ForwardedHeaders__Enabled", value: "true"},
{name: "Hosting__ForwardedHeaders__KnownNetworks__0", value: $cidr},
{name: "Storage__Provider", value: "S3"},
{name: "Storage__S3__BucketName", value: $bucket},
{name: "Storage__S3__Prefix", value: "work"},
{name: "Storage__S3__Region", value: $region},
{name: "Storage__S3__Credential", value: "InstanceRole"},
{name: "Storage__Inputs__0__Name", value: "default"},
{name: "Storage__Inputs__0__Path", value: "inputs/default"},
{name: "Storage__Inputs__0__PollIntervalSeconds", value: "30"},
{name: "Signing__Certificate__Source", value: "AwsKms"},
{name: "Signing__Certificate__AwsKms__Region", value: $region},
{name: "Signing__Certificate__AwsKms__KeyId", value: "alias/\($name)"},
{name: "Signing__Certificate__AwsKms__Credential", value: "InstanceRole"},
{name: "Signing__Certificate__AwsKms__S3__Bucket", value: $bucket},
{name: "Signing__Certificate__AwsKms__S3__Key", value: "signing-material/\($name).cer"},
{name: "Signing__Certificate__AwsKms__S3__Region", value: $region},
{name: "Signing__Certificate__AwsKms__S3__Credential", value: "InstanceRole"},
{name: "Signer__Endpoint", value: $signer},
{name: "Signer__FolderSweepIntervalSeconds", value: "300"}
] + (if $tenant == "" then [] else [
{name: "Auth__EntraId__TenantId", value: $tenant},
{name: "Auth__EntraId__ClientId", value: $client}
] end)),
secrets: ([
{name: "ConnectionStrings__Default", valueFrom: "\($sql):connectionString::"},
{name: "Auth__ApiKey", valueFrom: $auth},
{name: "Signing__PkiSdkLicense", valueFrom: $pki},
{name: "Signer__ApiKey", valueFrom: $sak},
{name: "Signer__WebhookSecret", valueFrom: $webhook}
] + (if $tenant == "" then [] else [
{name: "Auth__EntraId__ClientSecret", valueFrom: $entra}
] end))
}]}' > taskdef.json
aws ecs register-task-definition --region $REGION --cli-input-json file://taskdef.json \
--query taskDefinition.taskDefinitionArn

Um ARN vazio no taskdef.json indica um segredo do item 7.1 que não foi criado; o register-task-definition o recusa. Para um ambiente de homologação que assina com certificados de teste da Lacuna, troque Production por Staging e inclua Signing__TrustLacunaTestRoot=true (item 9.5).

9.5 Configuração do Bulk Signer​

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 endereço do item 1.1SimO 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.
  • 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 11 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).
Storage:Inputs:0:*A pasta monitorada para arquivos. Essas três chaves podem ser omitidas em uma implantação que assina somente documentos do Signer.
ASPNETCORE_ENVIRONMENT, Signing:TrustLacunaTestRootPara um ambiente de homologação que assina com os certificados de teste da Lacuna, defina Staging e inclua Signing__TrustLacunaTestRoot=true (Configuração).

9.6 Serviço​

aws ecs create-service --region $REGION --cluster $NAME --service-name bulksigner --task-definition $NAME \
--desired-count 1 --launch-type FARGATE \
--deployment-configuration 'minimumHealthyPercent=0,maximumPercent=100,deploymentCircuitBreaker={enable=true,rollback=false}' \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SG_TASK],assignPublicIp=ENABLED}" \
--load-balancers targetGroupArn=$TG,containerName=bulksigner,containerPort=8080 \
--health-check-grace-period-seconds 300
ParâmetroValorRequisito
minimumHealthyPercent, maximumPercent0, 100Toda implantação para a task em execução antes de iniciar a nova.
--desired-count1Não deve ser aumentado. Auto scaling não deve ser associado.
deploymentCircuitBreakerenable=true, rollback=falseUma implantação que falha é interrompida em vez de fazer rollback, o que iniciaria uma segunda task.

O primeiro boot aplica as migrations do banco. Aguarde o serviço estabilizar:

aws ecs wait services-stable --region $REGION --cluster $NAME --services bulksigner

10. 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), o segredo não chegou à task. Verifique a revisão da definição de task que o serviço está executando.

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

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

11.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 9.3 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.

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

11.3 Certificado separado para o perfil​

Para usar um certificado diferente do certificado do perfil default, repita o passo 3 com outro alias e outra chave de objeto. Inclua o ARN da nova chave na instrução kms: do task role antes de criar o perfil; do contrário, o salvamento é recusado em kms:GetPublicKey.

12. O webhook no Lacuna Signer​

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

12.2 Correspondência entre o Lacuna Signer e o Bulk Signer​

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

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.

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

13. Teste de assinatura​

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

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

13.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. As operações a seguir são específicas da integração com o Lacuna Signer:

OperaçãoProcedimentoObservações
Implantação (atualização de versão ou rotação de segredo)Pause o pipeline (POST /api/pipeline/pause), aguarde até não haver job em Processing ou Verifying, atualize o serviço 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 do segredo do webhookArmazene um novo valor aleatório em $NAME/signer-webhook-secret, force uma nova implantação 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, force uma nova implantação 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 certificadoImporte o novo PFX com um novo alias (passo 3), inclua a chave no task role, defina a nova chave e o novo .cer na página do perfil (Editar certificado) e reinicie.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 um ambiente implantadoExecute o item 9.3, gere de novo o taskdef.json do item 9.4 com TENANT_ID e CLIENT_ID definidas, registre a nova revisão e implante como na primeira linha desta tabela.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.Remover as variáveis Signer__* 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.

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 10 e 13).
GET/api/profiles/{name}Chave de APIVerificação do perfil (passo 11).
GET/api/jobs?profile={name}Chave de APITeste de assinatura (passo 13).
GET/api/metricsChave de APITeste de assinatura (passo 13).
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 11 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 12).
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 4. Um diretório --config vazio não resolve.
Health check path 'C:/Program Files/Git/api/ready' must begin with a '/' characterAWS CLI no Git Bash, item 8.2O Git Bash converteu /api/ready em um caminho do Windows.Execute export MSYS_NO_PATHCONV=1 (Variáveis de shell) e repita o item 8.2 a partir do create-target-group.
AADSTS50011Microsoft Entra ID, no loginA URI de redirecionamento enviada é http://… ou não corresponde ao registro de aplicativo.Confirme as configurações Hosting:ForwardedHeaders do item 9.4 e a URI https://<host>/signin-oidc do registro (item 9.3).
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.Defina as duas chaves (passo 9).
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.Gere o segredo de novo (item 7.1).
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 12) ou o security group (passo 5).
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 a configuração (passos 1 e 9).
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 (passos 3 e 7). 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