Pular para o conteúdo principal

Implantando no Amazon ECS Fargate

Esta página especifica uma implantação do Bulk Signer em instância única na Amazon Web Services. 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 uma pasta monitorada de entrada em um bucket do Amazon S3;
  • a chave de assinatura no AWS KMS.

Este não é um tipo de instalação à parte. É o alvo Docker executado por um orquestrador gerenciado, e cada configuração desta página é uma chave de configuração comum. O procedimento foi validado de ponta a ponta em um ambiente de homologação: um PDF foi enviado por HTTPS, assinado em PAdES na política AD-RB da ICP-Brasil com um certificado e-CNPJ cuja chave estava armazenada no AWS KMS, e recuperado do S3.

Versão mínima: 2.20.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 S3 não oferece lease, então não é possível distinguir uma segunda instância ativa de uma instância que parou de responder. O serviço do ECS do passo 8 é 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​

O Bulk Signer no Amazon ECS Fargate: uma task atrás de um Application Load Balancer, com RDS for SQL Server, S3, AWS KMS, Secrets Manager e CloudWatch Logs

Roles do IAM​

RoleAssumido porFinalidade
Execution role ($NAME-exec)Amazon ECSBaixa a imagem, escreve o stream de log e lê os segredos injetados no ambiente do container.
Task role ($NAME-task)Bulk SignerA identidade com que a aplicação é executada. É a credencial InstanceRole citada por todas as configurações de S3 e KMS desta página.

Nenhum dos dois roles armazena uma chave de acesso de longa duração. A única credencial sujeita a rotação manual é a senha do login SQL na connection string, porque o RDS for SQL Server não oferece autenticação de banco de dados pelo IAM.

Seleção de componentes​

ComponenteSeleçãoJustificativa
ComputaçãoECS Fargate, uma task, 1 vCPU / 2 GBA imagem é executada sem alterações. Uma task do Fargate não tem disco que persista além da própria task, o que determina as quatro linhas seguintes.
Banco operacionalRDS for SQL Server 2022O SQLite exige um disco durável. A edição Express é suficiente para um ambiente de homologação e limita o banco a 10 GB; ambientes de produção exigem a edição Web ou Standard.
Compartilhamento de trabalho e entradasUm bucket S3 (Storage:Provider = S3)Não há disco persistente disponível. O bucket guarda processing/, output/ e error/ sob um prefixo e a pasta monitorada sob outro.
Key ring de sessãoBanco operacional (Auth:SessionKeyRing:Placement = OperationalStore)Com qualquer outro posicionamento, o key ring é gravado no disco da task, e cada implantação encerra todas as sessões.
Chave de assinaturaAWS KMS (Source = AwsKms)A chave nunca sai do KMS, e cada assinatura é uma chamada kms:Sign registrada pelo AWS CloudTrail.
Certificado (.cer)Um objeto do S3 no mesmo bucketNão há caminho em disco disponível para um arquivo de certificado. O objeto é lido uma vez, no boot.
Logsstdout → CloudWatch LogsO log em arquivo continua sendo escrito, mas em um disco descartado junto com a task.
Backup do bancoBackups automáticos do RDSBackup:Enabled é recusado com Database:Provider = SqlServer.

Pré-requisitos​

RequisitoEspecificação
FerramentasAWS CLI v2 autenticado (aws sts get-caller-identity precisa responder) e Docker para copiar a imagem. Para a importação da chave no passo 2: 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.
Material de assinaturaO PFX cuja chave vai assinar, ou uma chave do KMS e o certificado que uma Autoridade Certificadora (AC) emitiu para ela.
Licenças e chavesA licença do PKI SDK (obrigatória). A chave de API do Lacuna Signer e a licença do Web PKI, se essas integrações forem usadas.
DNSUm hostname e permissão de escrita na zona de DNS autoritativa para ele.
Identifique a zona de DNS autoritativa

Um domínio pode ter uma hosted zone do Route 53 na conta e ainda assim estar delegado a outro provedor (Azure DNS, Cloudflare, um registrador). Registros escritos em uma zona não autoritativa nunca são resolvidos, e o certificado do ACM solicitado no passo 7 permanece em Pending validation indefinidamente. Confirme a delegação em um resolvedor público antes de começar:

dig +short NS example.com

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
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.
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; nenhum NAT gateway é necessário. Em produção, coloque a task e o banco em sub-redes privadas, atrás de um NAT gateway ou de VPC endpoints. Nenhuma outra configuração desta página muda.

1. O bucket​

Crie o bucket, bloqueie todo acesso público e configure a interrupção de uploads multipart incompletos após um dia:

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}}]}'

Crie a pasta monitorada. O Bulk Signer nunca cria uma pasta monitorada, então ela precisa existir antes do primeiro boot. No S3, uma pasta é um objeto marcador de tamanho zero:

aws s3api put-object --bucket $BUCKET --key inputs/default/

O bucket usa três prefixos:

PrefixoConteúdoCriado por
work/O compartilhamento de trabalho: processing/, output/ e error/O Bulk Signer, no boot
inputs/default/A pasta monitoradaO operador, antes do primeiro boot (o objeto marcador acima)
signing-material/O certificado de assinatura (.cer)O script de importação, no passo 2

Os prefixos não podem se aninhar. Uma pasta monitorada dentro do prefixo do compartilhamento de trabalho é recusada no boot.

O versionamento e o Object Lock devem permanecer desligados. Com o versionamento ligado, toda entrada excluída e toda cópia preparada são mantidas como versão anterior, inclusive arquivos de pagamento. As demais diferenças de comportamento de um compartilhamento de trabalho no S3 (ausência de lease, movimentação implementada como uma cópia seguida de uma exclusão e verificação de fingerprint antes de cada assinatura) estão documentadas em Configuração e em Instalação.

2. A chave de assinatura​

Para um PFX existente, o Import-PfxToKms.ps1 executa a importação completa:

  1. Cria no KMS uma chave RSA de assinatura com material de chave importado.
  2. Encapsula a chave privada localmente, no formato que o KMS exige.
  3. Importa o material de chave encapsulado.
  4. Compara a chave pública do KMS com o certificado, com a mesma verificação que o Bulk Signer faz no boot.
  5. Faz upload do arquivo .cer para o S3.

O script 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. O passo 8 o cita como alias/<nome> em Signing:Certificate:AwsKms:KeyId. 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 6; 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. Para fazer uma assinatura de teste pela linha de comando, inclua o usuário do IAM correspondente na lista de exceções.

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 e as verificações que o Bulk Signer faz ao abrir a chave estão documentados em Certificados.

Requisitos da chave, seja qual for a origem:

PropriedadeValor exigido
Tipo de chaveRSA 2048, 3072 ou 4096
Uso de chaveSign and verify

3. A imagem​

Copie a tag que a Lacuna indicou para instalação para um repositório do ECR na 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> # pede 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
RequisitoMotivo
Copie uma versão explícita, não latest.A tag enviada aqui é a tag que a definição de task fixa, e uma atualização é uma implantação agendada (passo 11).
Use um repositório na própria conta.O ECS baixa a imagem toda vez que uma task inicia. Um repositório local garante que um reinício não dependa da disponibilidade de um registry externo.
Plataforma linux/amd64.A definição de task especifica a arquitetura X86_64 (passo 8).
Docker Desktop no Windows: "The stub received bad data"

O token de login do ECR excede o tamanho que o Gerenciador de Credenciais do Windows aceita, então o docker login no ECR pode falhar com error storing credentials … The stub received bad data. Faça o login e o push com um diretório de configuração do Docker temporário (docker --config <pasta vazia> login … e docker --config <mesma pasta> push …) e exclua o diretório em seguida.

4. 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}]"

Como $NAME-task admite somente o balanceador de carga, nenhum outro host consegue acessar a porta do Bulk Signer para forjar forwarded headers. É essa condição que permite ao passo 8 confiar em forwarded headers vindos de toda a faixa da VPC.

5. O banco de dados​

5.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)
ParâmetroValorObservações
--enginesqlserver-exEdição Express. Em produção, use sqlserver-web ou sqlserver-se e avalie o Multi-AZ.
--engine-versionUma versão 16.00As versões 16.00 do engine são o SQL Server 2022. O primeiro comando lista as disponíveis na região.
--db-instance-classdb.t3.small
--storage-type, --allocated-storagegp3, 20
--master-usernamebsadminUsado somente pela task de configuração do item 5.3.
--manage-master-user-password—Armazena a senha mestre em um segredo do Secrets Manager que o próprio RDS controla e rotaciona.
--no-publicly-accessible—A instância só é acessível de dentro da VPC.
--backup-retention-period7Dias de backups automáticos.
--storage-encrypted—Criptografa o armazenamento em repouso.

5.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. ConnectionStrings:Default precisa da string completa, e o ECS consegue injetar uma chave de um segredo, mas não concatenar duas:

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

O segredo $NAME/sql-app tem a seguinte estrutura:

ChaveConteúdoConsumidor
usernamebulksigner—
passwordA senha gerada (40 caracteres, sem pontuação)A task de configuração do item 5.3 (APP_PASSWORD)
connectionStringA connection string abaixoO serviço (ConnectionStrings__Default)
Propriedade da connection stringValor
Servertcp:<endpoint do RDS>,1433
Initial CatalogBulkSigner
User IDbulksigner
PasswordA senha gerada
EncryptTrue
TrustServerCertificateTrue (veja o aviso abaixo)
TrustServerCertificate=True é uma limitação conhecida

A imagem do Bulk Signer não confia na autoridade certificadora do Amazon RDS. Sem essa propriedade, o primeiro login falha com The certificate chain was issued by an authority that is not trusted. Dentro de uma VPC cujo security group do banco admite somente a task, uma interceptação é um risco menor do que em uma rede compartilhada; ainda assim, o certificado do servidor de banco de dados não é verificado.

5.3 Criação do banco de dados​

O Bulk Signer cria as tabelas, mas nunca o banco (Escolhendo onde fica o banco operacional), e a instância é privada. Execute a configuração como uma task avulsa do Fargate na mesma VPC, com o sqlcmd da imagem mcr.microsoft.com/mssql-tools e este 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 de task de configuração exige:

ItemTipoValor
Imagem—mcr.microsoft.com/mssql-tools
DB_HOSTVariável de ambienteO endpoint do RDS ($DB_HOST)
MASTER_USERVariável de ambientebsadmin
MASTER_PASSWORDSegredo"<ARN do segredo gerenciado pelo RDS>:password::"
APP_PASSWORDSegredo"<ARN de $NAME/sql-app>:password::"
Execution role—$NAME-exec (passo 6)
Logs—O grupo awslogs do serviço, /ecs/$NAME (passo 8)
Rede—As sub-redes da task e o security group $NAME-task

Crie o execution role (passo 6) e o grupo de logs (passo 8) antes de executar a task de configuração. Execute-a uma vez com aws ecs run-task e leia o log. Código de saída 0 confirma que:

  • o banco BulkSigner existe com READ_COMMITTED_SNAPSHOT ligado, que o RDS, como qualquer SQL Server fora do Azure, não liga por padrão;
  • o login bulksigner existe e tem db_datareader, db_datawriter e db_ddladmin, e não db_owner.

6. Segredos e roles​

6.1 Segredos​

Gere o Auth:ApiKey, que é o X-API-Key da API REST e, a menos que o login pelo Microsoft Entra ID esteja ligado (passo 10), a chave de login do dashboard:

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

Informe manualmente os valores emitidos pela Lacuna, no console (Other type of secret → Plaintext, somente com o valor) ou por um prompt que não exibe o que é digitado. Não os registre no histórico do shell.

O inventário completo de segredos da implantação:

SegredoConteúdoCriado emInjetado como
$NAME/sql-appJSON com username, password e connectionStringItem 5.2ConnectionStrings__Default (chave connectionString)
$NAME/auth-api-keyUma chave aleatória de 48 caracteresItem 6.1Auth__ApiKey
$NAME/pki-sdk-licenseA licença do PKI SDKItem 6.1, manualmenteSigning__PkiSdkLicense
$NAME/signer-api-keyA chave de API do Lacuna Signer, no formato <aplicação>|<segredo>; a parte da aplicação pode ter espaçosItem 6.1, manualmenteSigner__ApiKey
$NAME/webpki-licenseA licença do Web PKIItem 6.1, manualmenteWebPki__License
$NAME/entra-client-secretO client secret do Entra IDPasso 10 (opcional)Auth__EntraId__ClientSecret
Segredo mestre gerenciado pelo RDSA senha de bsadminItem 5.1, pelo RDSMASTER_PASSWORD (somente na task de configuração)

6.2 Roles​

Os dois roles usam a seguinte política de confiança, 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, somente para a task de configuração).
$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>/*" }
]
}

O momento em que cada ação é chamada está especificado em Chamadas à AWS feitas pela task.

ECS Exec (opcional). Para abrir um shell na task em execução para diagnóstico, inclua as quatro ações de canal ssmmessages:* no task role e passe --enable-execute-command ao criar o serviço no passo 8.

7. HTTPS​

7.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 (veja Pré-requisitos) e aguarde a validação:

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

7.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}'
RecursoConfiguraçãoValorFinalidade
Balanceador de cargaEsquemainternet-facingPonto de entrada público.
Balanceador de cargaidle_timeout.timeout_seconds300Mantém aberto o WebSocket do dashboard entre atualizações.
Balanceador de cargarouting.http.drop_invalid_header_fields.enabledtrueRemove headers HTTP com nomes inválidos antes do encaminhamento.
Target groupProtocolo, porta, tipo de destinoHTTP, 8080, ipTasks do Fargate no modo awsvpc se registram pelo endereço IP.
Target groupHealth check/api/ready, a cada 30 s; 2 saudáveis, 3 não saudáveis; 200 esperadoVeja o contrato abaixo.
Target groupderegistration_delay.timeout_seconds30
Listener HTTPSPorta, política, ação443, ELBSecurityPolicy-TLS13-1-2-2021-06, encaminhar ao target group
Listener HTTPPorta, ação80, redirecionar para HTTPS com 301

Contrato do health check. O GET /api/ready é anônimo e responde 503 até o schema do banco ser aplicado. Assim, o balanceador de carga não encaminha tráfego a uma task que ainda não consegue atender requisições. Veja Endpoints HTTP.

7.3 DNS​

Aponte o hostname para o nome DNS do balanceador de carga, com um registro CNAME ou, se a zona estiver no Route 53, com um registro de alias.

8. O serviço​

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

8.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 3)
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://<o endereço do seu Lacuna Signer>" }
],
"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": "WebPki__License", "valueFrom": "<ARN de $NAME/webpki-license>" }
]

Cada chave está especificada na Referência de configuração. As seguintes regras valem para esta implantação:

  • Key ring de sessão. Auth__SessionKeyRing__Placement = OperationalStore armazena o key ring no banco. Sem essa configuração, o key ring é gravado no disco da task, e cada implantação encerra as sessões de todos os operadores e aprovadores. O primeiro boot com ela cria um key ring novo, o que encerra todas as sessões uma vez (Configuração).
  • Forwarded headers. Os endereços IP do balanceador de carga mudam e não podem ser listados um a um, então a faixa inteira da VPC é considerada confiável; o security group do passo 4 garante que somente o balanceador acessa a porta. Sem essas configurações, o Bulk Signer vê o esquema como http, os cookies dele não são marcados como seguros, uma aprovação registra o endereço do balanceador em vez do endereço do aprovador, e o login pelo Entra ID (passo 10) falha (Configuração).
  • Pasta monitorada. A entrada não especifica provider e, portanto, herda o S3 e o bucket do compartilhamento de trabalho. PollIntervalSeconds é obrigatório, porque objetos novos em um bucket só são detectados por listagem.
  • Seed do perfil de assinatura. Signing:Certificate é o seed (carga inicial) do perfil de assinatura default, aplicado somente no primeiro boot contra um banco vazio. Alterações posteriores são feitas na página Perfis de assinatura do dashboard. Toda credencial é InstanceRole, então nenhum perfil armazena segredo e Signing:ProfileSecretsKey não é necessário.
  • Lacuna Signer. As chaves do Signer são opcionais e devem ser definidas em conjunto: definir Signer__Endpoint torna Signer__ApiKey obrigatório no boot. Omita as duas para executar sem o Lacuna Signer. O endpoint é o endereço em que a própria API do Signer responde (GET <endpoint>/api/documents retorna 401 sem uma chave), que não é necessariamente o endereço da página inicial do produto. Verifique o endpoint com essa requisição.
  • Homologação. Para um ambiente que assina com os certificados de teste da Lacuna, defina ASPNETCORE_ENVIRONMENT como Staging e inclua Signing__TrustLacunaTestRoot=true (Configuração). Nenhuma das duas configurações é necessária para um certificado de produção da ICP-Brasil.

8.3 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. O padrão do ECS inicia a nova task primeiro, e duas tasks no mesmo compartilhamento de trabalho no S3 não podem ser detectadas. Cada implantação, portanto, causa uma breve indisponibilidade.
--desired-count1Não deve ser aumentado, pelo mesmo motivo. Não associe auto scaling.
--health-check-grace-period-seconds300O primeiro boot aplica todas as migrations do banco e testa o bucket. O período de carência impede que o balanceador de carga pare a task antes de ela ficar pronta.
deploymentCircuitBreakerenable=true, rollback=falseUma implantação que falha é interrompida em vez de fazer rollback, o que iniciaria uma segunda task.
assignPublicIpENABLEDNecessário para as chamadas de saída a partir de sub-redes públicas (veja Posicionamento de rede).
--load-balancersTarget group, container bulksigner, porta 8080Registra a task no target group do passo 7.

9. Verificação​

9.1 Prontidão e perfil​

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
curl -s -H "X-API-Key: $KEY" https://$HOST/api/profiles

Em um primeiro boot bem-sucedido, o /api/ready/details informa as seguintes verificações:

VerificaçãoResultado esperado
databaseAcessível, citando o host do RDS e BulkSigner.
storage-bucket:…Acessível, com toda requisição condicional recusada como esperado. É a verificação de inicialização que confirma que o S3 respeita as requisições condicionais de que o compartilhamento de trabalho depende.
input-folder:defaultEm execução, em s3://…/inputs/default.
work-share-ownerWritten at startup, not held. É o valor normal no S3, não uma falha.
pki-licenseCarregada.

O /api/profiles lista o perfil default com certificateSource = AwsKms e degraded = false. Isso confirma que a task leu o certificado do S3, acessou o KMS com o role dela e encontrou a chave correspondente ao certificado.

9.2 Teste de assinatura​

  1. Envie um PDF: POST /api/files, multipart, campo file. A resposta é 202 Accepted com o jobId.
  2. Consulte GET /api/jobs/{id} até que status seja Completed.
  3. Baixe o arquivo assinado: GET /api/jobs/{id}/output.
JOB=$(curl -s -H "X-API-Key: $KEY" -F "file=@sample.pdf" https://$HOST/api/files | jq -r .jobId)
curl -s -H "X-API-Key: $KEY" https://$HOST/api/jobs/$JOB | jq -r .status
curl -s -H "X-API-Key: $KEY" -o sample.signed.pdf https://$HOST/api/jobs/$JOB/output

Evidências esperadas:

  • O histórico do job registra a saída como s3://…/work/output/<nome>.signed.pdf.
  • O histórico de eventos do CloudTrail mostra a chamada Sign da task em alguns minutos.

Um nome de arquivo concluído fica reservado. Um novo envio com o mesmo nome retorna 409 com file.already-processed até o job ser excluído na página Jobs do dashboard.

9.3 Avisos de inicialização esperados​

Os seguintes avisos do log de inicialização são esperados nesta topologia e não indicam falha:

AvisoExplicação
Marcador do compartilhamento de trabalho written, not heldO S3 não oferece lease.
Key ring armazenado sem criptografiaO key ring é armazenado no banco e protegido pelo controle de acesso do banco.
ValidateCertificate=false no perfil criado pelo seedA configuração pode ser ligada na página do perfil.

10. Login pelo Microsoft Entra ID (opcional)​

Por padrão, a página de login do dashboard pede a chave de API. Quando uma seção Auth:EntraId está configurada, a página 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.

10.1 Registro de aplicativo​

Atribua ao ambiente um registro de aplicativo dedicado, não um compartilhado com outro ambiente, para que o acesso seja controlado pelas atribuições de role dele e o client secret dele siga um calendário de rotação próprio. O New-BulkSignerEntraApp.ps1 o cria:

pwsh ./New-BulkSignerEntraApp.ps1 -BaseUrl https://$HOST -DisplayName "Lacuna Bulk Signer (AWS)"
ParâmetroValorDescrição
-BaseUrlhttps://$HOSTA URL base pública; a URI de redirecionamento é derivada dela.
-DisplayNameLacuna Bulk Signer (AWS)O nome de exibição do registro de aplicativo.

O script mostra o client secret uma única vez. Armazene-o no Secrets Manager como $NAME/entra-client-secret (o execution role já lê secret:$NAME/*) e limpe o console. Atribua os usuários no centro de administração do Entra, em Enterprise applications → o aplicativo → Users and groups. Um usuário sem atribuição de role não consegue entrar.

10.2 Configuração​

Inclua as seguintes entradas na definição de task, registre a nova revisão e atualize o serviço:

{ "name": "Auth__EntraId__TenantId", "value": "<ID do diretório (tenant)>" },
{ "name": "Auth__EntraId__ClientId", "value": "<ID do aplicativo (client)>" }
{ "name": "Auth__EntraId__ClientSecret", "valueFrom": "<ARN de $NAME/entra-client-secret>" }

10.3 Pré-condições e efeitos​

ItemDescrição
Dependência dos forwarded headersO login monta a URI de redirecionamento a partir do esquema que o Bulk Signer vê. Sem as configurações de forwarded headers do passo 8, ele envia http://…/signin-oidc, que a Microsoft recusa com AADSTS50011.
Encerramento de sessõesSessões criadas pelo login com chave de API param de funcionar assim que a seção existe.
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.

10.4 Verificação​

Siga o primeiro redirecionamento sem entrar:

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

A URL precisa apontar para login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize, com o ID do aplicativo e redirect_uri=https://<host>/signin-oidc. O esquema https em redirect_uri é o valor a confirmar.

11. Operação​

OperaçãoProcedimentoObservações
Atualização de versãoEnvie uma nova tag de imagem (passo 3), registre uma revisão da definição de task que a cite e execute aws ecs update-service --cluster $NAME --service bulksigner --task-definition $NAME.A task em execução para antes de a nova iniciar. As migrations do banco rodam no boot da nova task.
Rotação de segredoAtualize o segredo e execute aws ecs update-service … --force-new-deployment.Um segredo rotacionado só chega ao container quando a task é substituída.
Rotação da senha do bancoExecute ALTER LOGIN no servidor, depois atualize $NAME/sql-app e force uma nova implantação.As duas mudanças devem ser feitas nessa ordem.
Renovação do certificadoExecute o script de importação com o novo PFX e um novo alias, faça upload do novo .cer, defina KeyId com o novo alias e reinicie.Mudanças de certificado valem a partir de um reinício.
PausaExecute aws ecs update-service … --desired-count 0 e aws rds stop-db-instance.O RDS reinicia automaticamente uma instância parada após sete dias. O balanceador de carga e o endereço IPv4 público continuam sendo cobrados.
DesativaçãoExclua os recursos na ordem abaixo.

Ordem de desativação:

  1. O serviço (escalado para 0 e depois excluído).
  2. O cluster.
  3. Os listeners, o balanceador de carga e o target group.
  4. Os registros de DNS.
  5. O certificado do ACM.
  6. A instância do RDS.
  7. Os security groups.
  8. Os roles do IAM.
  9. Os segredos.
  10. O bucket (esvaziado antes).
  11. O repositório do ECR.
  12. A chave do KMS, por último, com schedule-key-deletion e um período de espera de 7 a 30 dias.

As assinaturas já feitas continuam verificáveis após a exclusão da chave, porque a verificação usa a chave pública do certificado. A exclusão da chave elimina a capacidade de assinar com ela, a não ser que o backup do PFX seja importado de novo.

Referência de configuração​

Todas as chaves são definidas como variáveis de ambiente na definição de task (passo 8). Um __ no nome de uma variável de ambiente corresponde a : na chave de configuração.

Configurações principais​

Variável de ambienteValor nesta implantaçãoObrigatóriaDescrição
ASPNETCORE_ENVIRONMENTProduction (Staging em homologação)SimO ambiente de hospedagem.
Database__ProviderSqlServerSimO provider do banco operacional.
ConnectionStrings__DefaultSegredo $NAME/sql-app, chave connectionStringSimA connection string do SQL Server (item 5.2).
Auth__ApiKeySegredo $NAME/auth-api-keySimO X-API-Key da API REST; também a chave de login do dashboard quando o Entra ID não está configurado.
Auth__SessionKeyRing__PlacementOperationalStoreSim, nesta topologiaArmazena o key ring de sessão no banco, para que as sessões sobrevivam às implantações.
Hosting__ForwardedHeaders__EnabledtrueSimAplica os forwarded headers do balanceador de carga.
Hosting__ForwardedHeaders__KnownNetworks__0$VPC_CIDRSimA rede de onde os forwarded headers são considerados confiáveis.
Signing__PkiSdkLicenseSegredo $NAME/pki-sdk-licenseSimA licença do PKI SDK.
WebPki__LicenseSegredo $NAME/webpki-licenseSe o Web PKI for usadoA licença do Web PKI.

Compartilhamento de trabalho e pasta monitorada​

Variável de ambienteValor nesta implantaçãoObrigatóriaDescrição
Storage__ProviderS3SimColoca o compartilhamento de trabalho no S3.
Storage__S3__BucketName$BUCKETSimO bucket.
Storage__S3__PrefixworkSimO prefixo do compartilhamento de trabalho. Não pode conter uma pasta monitorada.
Storage__S3__Region$REGIONSimA região do bucket.
Storage__S3__CredentialInstanceRoleSimAutentica com o task role.
Storage__Inputs__0__NamedefaultSimO nome da pasta monitorada.
Storage__Inputs__0__Pathinputs/defaultSimO prefixo da pasta no bucket. O objeto marcador precisa existir (passo 1).
Storage__Inputs__0__PollIntervalSeconds30Sim, no S3O intervalo entre listagens da pasta.

Certificado de assinatura (seed do perfil default)​

Variável de ambienteValor nesta implantaçãoObrigatóriaDescrição
Signing__Certificate__SourceAwsKmsSimA origem do certificado.
Signing__Certificate__AwsKms__Region$REGIONSimA região da chave.
Signing__Certificate__AwsKms__KeyIdalias/$NAMESimA chave, pelo alias (passo 2).
Signing__Certificate__AwsKms__CredentialInstanceRoleSimAutentica no KMS com o task role.
Signing__Certificate__AwsKms__S3__Bucket$BUCKETSimO bucket que contém o certificado.
Signing__Certificate__AwsKms__S3__Keysigning-material/$NAME.cerSimA chave do objeto do certificado.
Signing__Certificate__AwsKms__S3__Region$REGIONSimA região do bucket do certificado.
Signing__Certificate__AwsKms__S3__CredentialInstanceRoleSimAutentica no S3 com o task role.
Signing__TrustLacunaTestRoottrueSomente em homologaçãoConfia na raiz de teste da Lacuna, para certificados de teste.

Essas chaves só são lidas no primeiro boot contra um banco vazio. Depois disso, o perfil é editado no dashboard.

Lacuna Signer (opcional)​

Variável de ambienteValor nesta implantaçãoObrigatóriaDescrição
Signer__Endpointhttps://<endereço do Lacuna Signer>NãoO endereço da API do Signer.
Signer__ApiKeySegredo $NAME/signer-api-keySim, quando Signer__Endpoint está definidoA chave de API do Signer, <aplicação>|<segredo>. A ausência dela interrompe a task no boot.

Microsoft Entra ID (opcional)​

Variável de ambienteValor nesta implantaçãoObrigatóriaDescrição
Auth__EntraId__TenantIdO ID do diretório (tenant)Para o login pelo Entra IDO tenant.
Auth__EntraId__ClientIdO ID do aplicativo (client)Para o login pelo Entra IDO registro de aplicativo.
Auth__EntraId__ClientSecretSegredo $NAME/entra-client-secretPara o login pelo Entra IDO client secret.

Configurações que não devem ser usadas​

ConfiguraçãoMotivo
Cluster:EnabledRecusada com um compartilhamento de trabalho no S3.
Backup:EnabledRecusada com Database:Provider = SqlServer; os backups automáticos do RDS a substituem.

Contratos de interface​

Endpoints HTTP​

Os endpoints usados nesta página. Requisições autenticadas enviam X-API-Key: <Auth:ApiKey>. Respostas de erro são corpos ProblemDetails (RFC 9457) com um campo code estável; clientes devem decidir pelo code, não pelo title (API REST).

MétodoRotaAutenticaçãoRespostaUsado por
GET/api/readyAnônima (a menos que Readiness:RequireApiKey = true)200, ou 503 quando uma verificação bloqueante falha. Corpo: { ready, checks: [{ name, ok }] }, sem detalhe.O health check do target group (passo 7); a verificação (passo 9).
GET/api/ready/detailsChave de APIO mesmo relatório com o detail de cada verificação, e a mesma regra de 200 / 503.A verificação (passo 9).
GET/api/profilesChave de API200, listando todos os perfis de assinatura, com certificateSource e degraded.A verificação (passo 9).
POST/api/filesChave de API202 Accepted com jobId, fileName, format e status (Queued).O teste de assinatura (passo 9).
GET/api/jobs/{id}Chave de API200 com o job, incluindo status e um array history.O teste de assinatura (passo 9).
GET/api/jobs/{id}/outputChave de APIO arquivo assinado. 409 job.output-unavailable se o job não estiver Completed; 410 job.output-gone se o arquivo saiu de output/.O teste de assinatura (passo 9).
GET/api/auth/entra-loginAnônimaUm redirecionamento para o endpoint de login da Microsoft.A verificação do Entra ID (passo 10).
GET/api/metricsChave de API (quando Metrics:RequireApiKey = true, o padrão)Formato de exposição do Prometheus.Não configurado nesta página (veja Limitações).

A API completa está especificada em API REST.

Endpoint externo​

MétodoRotaResposta esperadaFinalidade
GET<Signer__Endpoint>/api/documents401 sem uma chaveConfirma que Signer__Endpoint é o endereço da API do Signer.

Chamadas à AWS feitas pela task​

ServiçoAçãoRecursoQuando é chamada
AWS KMSkms:DescribeKey, kms:GetPublicKeyA chave de assinaturaUma vez, no boot, quando o certificado é aberto.
AWS KMSkms:SignA chave de assinaturaUma vez por arquivo assinado.
Amazon S3s3:GetObjectsigning-material/<nome>.cerUma vez, no boot.
Amazon S3s3:ListBucketO bucketA cada PollIntervalSeconds, para detectar arquivos na pasta monitorada; também pelas verificações de inicialização.
Amazon S3s3:GetObject, s3:PutObject, s3:DeleteObject, s3:AbortMultipartUploadObjetos do bucketDurante o processamento dos jobs. Uma movimentação entre prefixos é uma cópia seguida de uma exclusão.

As cotas de requisições do KMS são compartilhadas por todos os chamadores da conta e da região.

Referência de erros​

Códigos de erro​

CódigoOrigemCódigo de status HTTPSignificadoSolução
file.already-processedAPI do Bulk Signer409Um job Completed ou ativo já tem esse nome de arquivo.Exclua o job na página Jobs ou renomeie o arquivo.
job.output-unavailableAPI do Bulk Signer409O job não está Completed.Consulte GET /api/jobs/{id} até Completed.
job.output-goneAPI do Bulk Signer410O job está Completed, mas o arquivo dele não está mais em output/.—
job.staged-copy-changedJob do Bulk Signer— (registrado no job)A cópia preparada no bucket foi alterada entre a preparação e a assinatura. Nada foi assinado.Garanta que somente o Bulk Signer escreva em work/.
ThrottlingException, KeyUnavailableExceptionAWS KMS— (registrado no job)A cota de requisições do KMS foi excedida, ou a chave estava indisponível.Veja Diagnóstico de problemas.
AADSTS50011Microsoft Entra ID—A URI de redirecionamento não corresponde ao registro de aplicativo; ela é http://.Configure os forwarded headers do passo 8 com a faixa correta.
Erro AADSTS no login, após um período de funcionamento normalMicrosoft Entra ID—O client secret expirou.Faça a rotação do segredo em $NAME/entra-client-secret e force uma nova implantação.
The certificate chain was issued by an authority that is not trustedCliente do SQL Server—A imagem não confia no certificado do servidor RDS.Inclua TrustServerCertificate=True na connection string (item 5.2).
error storing credentials … The stub received bad dataDocker no Windows—O Gerenciador de Credenciais do Windows recusa o tamanho do token do ECR.Use um diretório --config temporário (passo 3).

A lista completa de códigos de erro da API está em API REST.

Sintomas​

SintomaCausaSolução
O certificado do ACM permanece em Pending validation.O CNAME de validação foi escrito em uma zona que não é autoritativa para o domínio.Confira os registros NS públicos e escreva o registro na zona autoritativa.
A task para no boot, e o log dela cita Signer:ApiKey.Signer__Endpoint está definido sem Signer__ApiKey.Defina as duas chaves ou nenhuma.
/api/ready retorna 503 e a verificação database está falhando.A task está em execução, mas o schema não foi aplicado. Se o detalhe cita uma cadeia de certificados não confiável, falta TrustServerCertificate=True na connection string. Caso contrário, o banco ainda não existe, ou o security group do banco não admite o grupo da task.Corrija a connection string (item 5.2), execute a task de configuração (item 5.3) ou corrija o security group (passo 4).
input-folder:default não está em execução.Falta o objeto marcador inputs/default/, ou o task role não tem s3:ListBucket no bucket.Crie o marcador (passo 1) ou corrija o task role (passo 6). Veja Diagnóstico de problemas.
O perfil default está degradado.O objeto do certificado no S3 não pode ser lido, o KMS recusou o task role ou o .cer não corresponde à chave do KMS.Confirme que o ARN do task role está na lista de exceções da negação na política da chave. O motivo aparece no /api/ready/details e na página do perfil; veja Diagnóstico de problemas.
Todos os usuários são desconectados a cada implantação.Auth__SessionKeyRing__Placement não é OperationalStore.Defina a chave (passo 8).

Limitações​

Não oferecidoAlternativa
Mais de uma instânciaAzure App Service (modo cluster).
Verificação do certificado do servidor de banco de dadosNenhuma nesta topologia; veja TrustServerCertificate=True no item 5.2.
Métricas no CloudWatchO /api/metrics serve texto do Prometheus atrás da chave de API. O Amazon Managed Service for Prometheus ou o agente do CloudWatch podem coletá-lo; nenhum dos dois é configurado nesta página.
Backup feito pelo Bulk SignerOs backups automáticos do RDS.
Ver em Markdown