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. Uma pessoa 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.

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.

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: uma pessoa 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
FerramentasAWS CLI v2 autenticado (aws sts get-caller-identity precisa responder), Docker para copiar a imagem e jq. 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.

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:

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

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:

aws ecr create-repository --repository-name bulksigner --region $REGION \
--image-tag-mutability IMMUTABLE --image-scanning-configuration scanOnPush=true
IMAGE=$ACCOUNT.dkr.ecr.$REGION.amazonaws.com/bulksigner:<versão>

docker login <registry-da-lacuna> --username <usuário-do-registry> # solicita o token
docker pull --platform linux/amd64 <registry-da-lacuna>/bulksigner:<versão>
docker tag <registry-da-lacuna>/bulksigner:<versão> $IMAGE

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

Copie uma versão explícita, e não latest. Os requisitos deste passo e a solução para a falha do docker login The stub received bad data no Docker Desktop para Windows estão especificados no passo 3 de Amazon ECS Fargate.

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

6.3 Criação do banco de dados​

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

#!/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];"

A definição da task de configuração, a estrutura do segredo $NAME/sql-app, a limitação de TrustServerCertificate=True e as condições confirmadas pelo código de saída 0 estão especificadas no passo 5 de Amazon ECS Fargate.

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

Os dois últimos segredos 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​

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

{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ecs-tasks.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "aws:SourceAccount": "<conta>" },
"ArnLike": { "aws:SourceArn": "arn:aws:ecs:<região>:<conta>:*" }
}
}]
}
RolePermissões
$NAME-execA 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 de configuração). Esse role lê a chave de API do Signer e o segredo do webhook para o container.
$NAME-taskA política inline abaixo.
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["kms:DescribeKey", "kms:GetPublicKey", "kms:Sign"],
"Resource": "arn:aws:kms:<região>:<conta>:key/<id da chave>" },
{ "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>/*" }
]
}

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 Definição de task​

ParâmetroValor
cpu1024
memory2048
networkModeawsvpc
runtimePlatform.cpuArchitectureX86_64
executionRoleArn$NAME-exec
taskRoleArn$NAME-task
Nome do containerbulksigner
Imagem do container$IMAGE (passo 4)
Porta do container8080
stopTimeout60
Driver de logawslogs, grupo /ecs/$NAME

A definição do container traz o seguinte ambiente e os seguintes segredos:

"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": "<o CIDR da VPC>" },
{ "name": "Storage__Provider", "value": "S3" },
{ "name": "Storage__S3__BucketName", "value": "<bucket>" },
{ "name": "Storage__S3__Prefix", "value": "work" },
{ "name": "Storage__S3__Region", "value": "<região>" },
{ "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": "<região>" },
{ "name": "Signing__Certificate__AwsKms__KeyId", "value": "alias/<nome>" },
{ "name": "Signing__Certificate__AwsKms__Credential", "value": "InstanceRole" },
{ "name": "Signing__Certificate__AwsKms__S3__Bucket", "value": "<bucket>" },
{ "name": "Signing__Certificate__AwsKms__S3__Key", "value": "signing-material/<nome>.cer" },
{ "name": "Signing__Certificate__AwsKms__S3__Region", "value": "<região>" },
{ "name": "Signing__Certificate__AwsKms__S3__Credential", "value": "InstanceRole" },
{ "name": "Signer__Endpoint", "value": "https://<endereço do Lacuna Signer>" },
{ "name": "Signer__FolderSweepIntervalSeconds", "value": "300" }
],
"secrets": [
{ "name": "ConnectionStrings__Default", "valueFrom": "<ARN de $NAME/sql-app>:connectionString::" },
{ "name": "Auth__ApiKey", "valueFrom": "<ARN de $NAME/auth-api-key>" },
{ "name": "Signing__PkiSdkLicense", "valueFrom": "<ARN de $NAME/pki-sdk-license>" },
{ "name": "Signer__ApiKey", "valueFrom": "<ARN de $NAME/signer-api-key>" },
{ "name": "Signer__WebhookSecret", "valueFrom": "<ARN de $NAME/signer-webhook-secret>" }
]

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

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, storage-bucket:…, work-share-owner, pki-license), /api/ready/details informa 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.

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>/, entre com a chave armazenada em $NAME/auth-api-key e 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

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