Instalação do Servidor
O Servidor do TrustBridge é distribuído como uma imagem de container Linux,
trustbridge-server:<version>, com duas formas de hospedagem suportadas. As duas executam
exatamente uma instância.
| Docker no Linux (esta página) | Azure App Service | |
|---|---|---|
| Banco de dados | SQL Server, ou PostgreSQL 16 ou posterior; ao lado do Servidor ou uma instância que você já opera | Azure SQL, com identidade gerenciada e sem senha |
| TLS | O Servidor termina o próprio, ou um proxy reverso que você informa o termina | A plataforma o termina |
| Cofre de Certificados | Disponível | Disponível |
| Quem programa um reinício | Você | A plataforma, para a própria manutenção |
A Lacuna Software fornece um pacote de implantação com a referência da imagem, os arquivos compose
(docker-compose.yml para SQL Server, docker-compose.postgresql.yml para PostgreSQL), um exemplo de
ambiente comentado (.env.example), a implantação em um único script (deploy-docker.ps1) e os
modelos de Política de Grupo para a frota. As instruções abaixo supõem que você tem esse pacote no host
de destino. Os exemplos usam o Servidor 1.24.0, imagem trustbridge-server:1.24.0.
Antes de escolher a forma de hospedagem
Cinco coisas valem para toda implantação e determinam como você a verifica.
Uma instância é uma regra, não uma recomendação. As estações de trabalho mantêm uma conexão SignalR persistente e não há backplane, então uma segunda instância não sabe nada das estações de trabalho conectadas à primeira. A falha é silenciosa: as sessões falham contra máquinas que estão conectadas e invisíveis. Nunca escale horizontalmente.
Toda implantação precisa de um Protetor do Chaveiro. Os dois chaveiros (key rings) do Servidor, o
que está por trás dos cookies do console administrativo e o que está por trás do Cofre de
Certificados, são linhas no próprio banco de dados dele, seladas sob um único certificado X.509 que
você fornece como configuração (KeyRingProtector:Certificate). O Servidor se recusa a iniciar sem
ele, com ou sem cofre. Faça backup do PKCS#12 permanentemente, em algum lugar que a plataforma não
consiga acessar. Veja Segurança.
Toda implantação precisa de uma licença do PKI SDK. A licença do Lacuna PKI SDK
(Certificates:Validation:License) é a licença com que a frota inteira funciona: o Servidor entrega a
própria a toda máquina Admitida, e nada lê um certificado sem ela. Por isso o Servidor se recusa a
iniciar sem uma, guarde ele certificados próprios ou não. A imagem não traz licença. Uma licença
vencida não impede o Servidor de iniciar, mas nada na frota nem no cofre consegue assinar até que ela
seja renovada.
O Servidor atualiza o próprio schema. A cada inicialização, antes de começar a escutar, ele
aplica ao banco de dados as migrações pendentes (Database:MigrateOnStart, padrão true). Uma
implantação é uma imagem e uma connection string; uma atualização é uma nova tag de imagem. O custo
disso é uma permissão permanente de DDL (db_ddladmin, ou, no PostgreSQL, CREATE no schema public)
para a identidade com que o Servidor se conecta, ou o interruptor definido como false e um DBA
aplicando as migrações antes.
| O banco de dados está | O que o Servidor faz |
|---|---|
| Atualizado | Inicia. Nenhum DDL é executado. |
| Atrasado | Aplica as migrações que faltam, registra os nomes delas no log e então inicia. |
Atrasado, com MigrateOnStart=false | Recusa-se a iniciar, citando as migrações e o comando a executar. |
| À frente deste build (um rollback que atravessa uma migração) | Recusa-se a iniciar. Código antigo sobre um schema mais novo responderia errado em vez de falhar. |
| Inacessível | Tenta de novo por cerca de noventa segundos e então se recusa a iniciar, informando quanto tempo esperou. |
Cada comando de migração tem um limite próprio de trinta minutos; nada limita uma atualização como um todo.
"Iniciou" não é "funciona". Um Servidor em execução responde GET / com 200 mesmo com o banco de
dados inacessível, de propósito, para que uma instabilidade do banco não seja uma indisponibilidade
que alguém precise encerrar. A verificação que toca o banco de dados é GET /healthz: ela faz uma ida
e volta ao SQL e responde Healthy (200) ou Unhealthy (503), não exige credencial e não grava
nenhuma linha na Trilha de Auditoria. Aponte o seu monitoramento para ela.
SQL Server ou PostgreSQL
No Docker, o Servidor roda no SQL Server ou, desde a 1.20.0, no PostgreSQL 16 ou posterior. Uma
única imagem traz os dois drivers, e Database:Engine (SqlServer,
o padrão, ou PostgreSql) escolhe um na inicialização. O PostgreSQL não precisa de nenhuma extensão. A
forma com App Service continua no Azure SQL (Azure App Service).
Uma implantação escolhe o mecanismo de banco de dados uma vez, na instalação, e nunca muda. As
implantações existentes continuam no SQL Server sem nenhuma alteração. Nada move a Trilha de Auditoria,
o Cofre de Certificados, os chaveiros e o registro de estações de trabalho de uma instalação em
funcionamento para o outro mecanismo, e alterar Database:Engine numa implantação que já existe não é
uma migração: aponta o Servidor para um banco de dados diferente e vazio. Onde os mecanismos diferem
para o operador, esta página diz isso ao lado da forma com SQL Server: o arquivo compose e a senha dele
(passo 3), a permissão de que a etapa do schema precisa e o script idempotente (passo 4), e os backups.
O caminho curto: um script
O deploy-docker.ps1 (Servidor 1.21.0 e posteriores) faz os passos abaixo na forma com PostgreSQL e
coloca o REST PKI Core ao lado, com um bucket do RustFS para os documentos assinados do REST PKI Core.
Ele precisa do PowerShell 7.2 ou posterior e do Docker com o plugin Compose, no Linux ou no Windows com o
Docker Desktop:
pwsh ./deploy-docker.ps1
Ele pergunta três coisas: o nome de host que as estações de trabalho e os navegadores usam para
chegar a esta máquina (por padrão, o nome DNS desta máquina; nunca localhost, porque o container do
Servidor chama o REST PKI Core por esse nome), a licença do PKI SDK (o caminho do
LacunaPkiLicense.config ou da licença binária, ou o base64 dela) e o e-mail do primeiro
Administrador. Cada uma pode ser passada como parâmetro: -HostName, -PkiLicense e -AdminEmail.
Ele gera todo o resto: todas as senhas, as chaves do RustFS, o Protetor do Chaveiro, a chave de
criptografia e a senha de root do REST PKI Core, e um certificado TLS para o nome de host emitido por
uma AC própria da implantação, cuja chave é descartada depois de emitir esse único certificado. Com
-TlsCertificate (e -TlsCertificatePassword), ele usa um PKCS#12 seu, para o TrustBridge e para o REST
PKI Core. Depois grava o .env e o docker-compose.yml no diretório da implantação (-Path, por padrão
~/trustbridge), inicia cinco containers (o PostgreSQL 16, com um banco para o TrustBridge e outro para o
REST PKI Core, o RustFS, um container de execução única que cria o bucket, o REST PKI Core e o
Servidor), espera o /healthz responder Healthy e mostra os endereços e as credenciais. O TrustBridge
responde na porta 443 e o REST PKI Core na 8443 (-HttpsPort, -RestPkiPort); o console do RustFS
escuta só nesta máquina. O REST PKI Core fica configurado para assinar ICP-Brasil AD-RB sem carimbo de
tempo.
Execute-o de novo quando quiser. Ele mantém todos os valores que já estão no .env, então uma
segunda execução atualiza (passe -Image trustbridge-server:<version>) ou repara, e nunca troca as
chaves: um Protetor do Chaveiro novo, ou uma chave de criptografia nova do REST PKI Core, deixaria
ilegível tudo o que foi guardado sob a anterior. Ele reescreve o docker-compose.yml a cada execução,
então uma alteração manual nesse arquivo não sobrevive. Ele recusa uma imagem anterior à 1.20.0, a
primeira release que roda no PostgreSQL. A imagem precisa estar na máquina ou poder ser baixada: entre no
registry que a guarda (docker login) e passe o nome completo dela em -Image.
O que ele não decide por você:
- O REST PKI Core só lê a licença binária da Lacuna. Se você der ao script o
LacunaPkiLicense.config, ele pede também a licença binária (ou passe-a em-RestPkiLicense). Se der a licença binária, os dois serviços a usam. - Ninguém confia na AC própria dele. As estações de trabalho só se conectam depois de confiar em
tls/trustbridge-ca.crt, no diretório da implantação (Autoridades de Certificação Raiz Confiáveis, por Política de Grupo para uma frota). Caso contrário, execute-o de novo com-TlsCertificatee um certificado em que elas já confiem (passo 2). - Ligar o TrustBridge ao REST PKI Core é uma segunda execução. A chave de API só existe depois que o
REST PKI Core está no ar. Entre no REST PKI Core como
root, crie uma aplicação com o role Worker e uma chave para ela, e execute o script com-ConnectRestPki. Ele pede a chave e o contexto de segurança (por padrão, o contexto padrão ICP-Brasil do REST PKI Core), ou os recebe em-RestPkiApiKeye-RestPkiSecurityContextId, e aponta o Servidor para o REST PKI Core desta implantação. Depois, ligue na tela Aplicações Cliente as Aplicações Cliente que devem assinar com ele. Veja REST PKI Core sobre o contexto de segurança, a política de assinatura e as marcas nas páginas. - O Signer fica como você deixar. Ele fica sem configuração, a menos que
SIGNER_BASEADDRESSeSIGNER_APIKEYestejam definidas no.env; a execução seguinte as mantém. O Cofre de Certificados fica desligado no arquivo que ele grava. - O diretório da implantação é o backup. O
.envdele contém as únicas cópias do Protetor do Chaveiro e da chave de criptografia do REST PKI Core; perder qualquer uma torna ilegível o que foi guardado sob ela. A primeira execução também deixa o protetor embackup/key-ring-protector.pfx: copie-o para um lugar seguro e depois apague-o da máquina.
Leia o restante desta página antes de contar com uma implantação assim em produção: tudo o que vem abaixo é o que o script faz por você.
Pré-requisitos
- Um host Linux com Docker Engine e o plugin Compose.
- Um certificado TLS em PKCS#12 cuja cadeia todas as estações de trabalho já considerem confiável, para o Servidor ou para o proxy reverso na frente dele. O Workstation Service recusa uma cadeia não confiável, e o sintoma são estações de trabalho que nunca aparecem, sem nenhum erro no Servidor. Um certificado autoassinado não serve. Use a sua AC interna ou uma pública.
- Um Protetor do Chaveiro, um segundo PKCS#12, bem diferente do primeiro. O passo 3 abaixo o cria.
- A licença do Lacuna PKI SDK, o
LacunaPkiLicense.configemitido pela Lacuna. Obrigatória em toda implantação, com ou sem cofre. - Credenciais do Lacuna Signer: o endereço base e a chave de API no formato
application|secretque o Signer publica. As duas ou nenhuma. Um endereço sem chave impede a inicialização. - Somente se uma Aplicação Cliente for assinar os documentos com o REST PKI Core: o endereço dele, a chave de API de uma aplicação com o role Worker e um contexto de segurança. Veja REST PKI Core.
- Um banco de dados: SQL Server, ou PostgreSQL 16 ou posterior. Cada arquivo compose pode executar um ao lado do Servidor, mas leia a observação sobre a edição do SQL Server.
- Somente se você for ligar o Acesso com Certificado no console administrativo: uma licença do Lacuna Web PKI no formato de uma linha, citando o nome de host que as pessoas digitam para chegar ao console.
1. O diretório do host
Um diretório, que o Servidor apenas lê, como uid 1654:
sudo mkdir -p /etc/trustbridge/tls /opt/trustbridge
O Servidor não grava nada no seu disco. O único diretório em que ele grava é /tmp, dentro do
container, em três lugares:
| Diretório | O que contém |
|---|---|
/tmp/trustbridge-pki | Os caches do PKI SDK de emissores e LCRs baixados, e o arquivo pelo qual a licença da PKI é entregue ao SDK |
/tmp/trustbridge-logs | O próprio arquivo de log rotativo do Servidor, que a tela Log do Servidor lê |
/tmp/trustbridge-releases | Uma cópia de cada Versão da Estação de Trabalho guardada na tela Versões da Estação de Trabalho, de onde são servidos os downloads da frota, e um upload enquanto o Servidor o lê |
Os três são cópias de algo guardado em outro lugar: os caches se preenchem de novo, toda linha de log
foi antes para o log do container, e cada cópia de uma versão é reconstruída a partir da linha dela no
banco de dados no próximo download. Nada ali precisa de backup. O terceiro é o que cresce: uma Versão
da Estação de Trabalho tem cerca de 100 MB, uma cópia por versão guardada. Se esse diretório não puder
ser gravado, ou se o /tmp estiver cheio, a tela Versões da Estação de Trabalho recusa todo upload,
citando o motivo, enquanto os downloads continuam sendo servidos a partir do banco de dados. Se você
montar um tmpfs sobre o /tmp, monte um volume em disco sobre o próprio
/tmp/trustbridge-releases: um tmpfs conta contra o limite de memória do container.
2. TLS
No arquivo compose padrão não há nada na frente deste Servidor, então ele termina o próprio TLS:
sudo cp your-certificate.pfx /etc/trustbridge/tls/server.pfx
sudo chown 1654:1654 /etc/trustbridge/tls/server.pfx
sudo chmod 400 /etc/trustbridge/tls/server.pfx
O container escuta na 8443; o host publica a 443. Um processo sem privilégios não consegue
abrir a porta 443, então o dockerd a abre e encaminha. Esse mapeamento já está no arquivo compose.
Para fazer Assinaturas de teste (pela página Assinatura de teste do console administrativo, ou pela
pública), adicione um listener HTTP simples no próprio loopback do container:
ASPNETCORE_URLS: https://+:8443;http://localhost:8080. Uma execução chama o próprio contrato do
Servidor, e o certificado TLS é emitido para o nome que as pessoas digitam, que não é localhost.
Nada fora do container consegue acessar esse listener, e o arquivo compose continua publicando só a
8443. Veja A Assinatura de teste.
Atrás de um proxy reverso que você já opera
Se preferir terminar o TLS em um proxy (nginx, HAProxy, um balanceador de carga), o Servidor fala HTTP simples com ele:
-
Remova as variáveis
Kestrel__Certificates__*do arquivo compose e definaASPNETCORE_URLS: http://+:8443. O Kestrel configurado para servir HTTPS sem certificado não inicia. -
Publique a porta somente no loopback:
127.0.0.1:8443:8443no lugar de443:8443, que agora é do proxy. Assim, nada chega ao container pela porta publicada além do próprio host. -
Informe o proxy em
ForwardedHeaders__TrustedProxies__0(e__1,__2para mais entradas: a lista precisa de índice mesmo com uma entrada só). Use o endereço do qual o container vê o proxy se conectar, que nem sempre é o que você conhece:- Um proxy no host do Docker se conecta a partir do gateway da rede bridge
(
docker network inspect). - Um proxy em outro container na mesma rede do compose se conecta a partir de um endereço da sub-rede dessa rede. Listar a sub-rede confia em todos os containers dela.
Sem isso, o Servidor toma o seu proxy por todos os chamadores: toda linha de auditoria registra o endereço do proxy, e todos os navegadores na página de login e todas as máquinas pedindo admissão compartilham um único limite por endereço. O log de inicialização diz o que o Servidor entendeu da lista, numa linha que começa com
Forwarded headers:. Se as linhas de auditoria continuarem mostrando o endereço do proxy, a lista não contém o endereço do qual o proxy se conecta. As regras das entradas estão emForwardedHeaders:TrustedProxies. - Um proxy no host do Docker se conecta a partir do gateway da rede bridge
(
O proxy precisa enviar o endereço e o esquema de quem chamou, repassar o header Host (o Servidor
nunca lê X-Forwarded-Host, e o nginx envia o nome do upstream se não for instruído do contrário) e
transportar WebSockets. O canal das estações de trabalho e o console administrativo são conexões
WebSocket. Sem os headers de upgrade, os dois recorrem a long polling: o console continua funcionando,
só que mais lento, e a frota se conecta como uma sequência de requisições de polling, cada uma
verificada contra o banco de dados. No nginx, no nível http, uma vez:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
O nginx na borda, o único proxy na frente do Servidor, substitui o header de endereço pelo único endereço que ele viu:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
O nginx atrás de um balanceador de carga que termina o TLS, com os dois listados em TrustedProxies,
mantém o que o balanceador escreveu: acrescenta ao endereço e repassa o esquema do balanceador, porque
o $scheme dele é o http que o balanceador usou para falar com ele.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
Defina tudo isso num lugar só: o nginx deixa de herdar as linhas proxy_set_header de um bloco
externo assim que um location define uma própria.
Deixe passar uma Versão da Estação de Trabalho. Um upload para
/admin/workstation-releases/upload é uma única requisição de até 256 MB. Aumente o limite de corpo
do proxy para esse caminho: o client_max_body_size do nginx é 1 MB por padrão, o que recusa todas as
versões.
Não use ASPNETCORE_FORWARDEDHEADERS_ENABLED. Esse interruptor é para o App Service: ele confia em
quem se conectar, então qualquer cliente que consiga chegar à porta do container sem passar pelo proxy
poderia informar o próprio endereço. Fora do App Service, o Servidor registra um Warning na
inicialização quando ele está definido, e defini-lo junto com TrustedProxies impede a
inicialização. Se uma implantação anterior sua o definiu, remova-o e informe o proxy.
3. Configuração
cd /opt/trustbridge
cp <package>/docker-compose.yml .
cp <package>/.env.example .env
chmod 600 .env
Para o PostgreSQL, copie em vez disso o docker-compose.postgresql.yml como docker-compose.yml,
para que todos os comandos docker compose desta página o encontrem como estão escritos (veja
PostgreSQL em vez de SQL Server).
O arquivo compose deve ficar no controle de versão e não contém nenhum segredo; o .env contém todos
os segredos e não pode ser versionado. A página Configuração do Servidor lista
todas as chaves que o Servidor aceita; as que esta forma de hospedagem exige estão abaixo.
Variável do .env | O que é |
|---|---|
TRUSTBRIDGE_IMAGE | A imagem, com uma tag específica, como trustbridge-server:1.24.0. Nunca latest. O arquivo compose do SQL Server usa a própria tag quando a linha falta; o do PostgreSQL não tem tag de reserva e se recusa a iniciar sem ela. |
MSSQL_SA_PASSWORD | A senha do sa do SQL Server incluído (8 ou mais caracteres de três classes de caracteres), lida pelo docker-compose.yml. Deixe-a vazia no PostgreSQL. |
POSTGRES_PASSWORD | A senha do PostgreSQL incluído, lida pelo docker-compose.postgresql.yml. Só letras e dígitos (openssl rand -hex 24), porque ela entra numa connection string do jeito que está escrita. Deixe-a vazia no SQL Server. |
TLS_CERT_PASSWORD | A senha de /etc/trustbridge/tls/server.pfx. Desnecessária atrás de um proxy reverso. |
KEY_RING_PROTECTOR_CERTIFICATE | O Protetor do Chaveiro, uma linha de base64. Obrigatório. |
KEY_RING_PROTECTOR_PASSWORD | Somente se esse PKCS#12 tiver senha. |
PKI_LICENSE | A licença do Lacuna PKI SDK, em base64 em uma linha. Obrigatória. O arquivo compose não inicia o Servidor com ela vazia. |
VAULT_ENABLED | false, a menos que este Servidor vá guardar Certificados do Cofre. |
SIGNER_BASEADDRESS, SIGNER_APIKEY | O Lacuna Signer. As duas ou nenhuma. |
ADMIN_BOOTSTRAP_EMAIL, ADMIN_BOOTSTRAP_PASSWORD | O primeiro administrador. Lidas somente enquanto a tabela de contas estiver vazia; remova-as depois do primeiro login. |
Usando um SQL Server que você já opera
Essa é a forma normal em produção. Exclua o serviço sqlserver do arquivo compose, remova o
depends_on e aponte ConnectionStrings__TrustBridge para a instância real:
ConnectionStrings__TrustBridge: >-
Server=sql.example.com,1433;Database=TrustBridge;User Id=trustbridge;
Password=${DB_PASSWORD};Encrypt=True
A identidade precisa de db_datareader, db_datawriter e, para que o Servidor aplique as próprias
migrações, db_ddladmin. O banco de dados em si precisa existir. Aplicar uma migração nunca cria um
banco de dados, exceto na forma com banco incluído, em que o Servidor se conecta como sa e pode
criá-lo.
Se você mantiver o banco de dados incluído, note que o arquivo compose define MSSQL_PID=Developer,
que a Microsoft licencia somente para desenvolvimento e teste, não para produção. Defina uma
edição para a qual você tenha licença, ou use um SQL Server que a sua organização já opera e do qual
já faz backup. Nada vai avisar você.
PostgreSQL em vez de SQL Server
cp <package>/docker-compose.postgresql.yml /opt/trustbridge/docker-compose.yml
No .env, defina POSTGRES_PASSWORD, deixe MSSQL_SA_PASSWORD vazia e mantenha TRUSTBRIDGE_IMAGE.
A imagem só lê a senha quando inicializa um volume vazio, então trocá-la depois é um ALTER ROLE dentro
do banco de dados, além da linha no .env.
O arquivo define três coisas de forma diferente:
Database__Engine: PostgreSql, que o Servidor lê uma vez, na inicialização.- Uma connection string na grafia do Npgsql (
Host=,Username=eGSS Encryption Mode=Disable, que mantém fora do log uma verificação inofensiva do Kerberos). Ela aponta para o próprio PostgreSQL, nunca para um pooler em modo de transação (o do PgBouncer, ou o que o Azure Database for PostgreSQL oferece na porta 6432): o lock da etapa do schema pertence a uma única sessão do banco. - Um serviço
postgres:16, não publicado no host, cuja imagem cria o usuáriotrustbridgecomo superusuário e o bancotrustbridgena primeira inicialização do volume vazio, para que o Servidor tenha permissão de criar o próprio schema, como osatem no arquivo do SQL Server.
Usando um PostgreSQL que você já opera (16 ou posterior): exclua o serviço postgres, remova o
depends_on e aponte a connection string para ele com SSL Mode=Require ou mais rigorosa, porque o
padrão do Npgsql volta para texto simples quando o servidor não oferece TLS:
ConnectionStrings__TrustBridge: >-
Host=pg.example.com;Port=5432;Database=trustbridge;Username=trustbridge;
Password=${DB_PASSWORD};SSL Mode=Require;GSS Encryption Mode=Disable
A identidade precisa de CREATE no schema public (ou ser dona dele) de um banco vazio que um DBA
criou, ou de CREATEDB para que o Servidor o crie. Não há edição de licença a escolher: o PostgreSQL tem
uma só.
O Protetor do Chaveiro
Crie um. Um certificado RSA autoassinado é o formato certo: nada nunca o valida como certificado, então não há nada que uma AC precise atestar. Ele é um par de chaves com um thumbprint.
openssl req -x509 -newkey rsa:2048 -nodes -days 18250 \
-keyout protector.key -out protector.crt \
-subj "/CN=TrustBridge Key Ring Protector"
openssl pkcs12 -export -inkey protector.key -in protector.crt -out protector.pfx -passout pass:
base64 -w0 protector.pfx
- RSA, não ECDSA. O framework sela o material de chave por meio de
EncryptedXml, que só usa RSA. Um certificado ECDSA é recusado na inicialização. - Cinquenta anos de validade. As datas de um protetor nunca são verificadas, mas um protetor que você se sinta obrigado a substituir é um que, de qualquer forma, terá de guardar para sempre.
base64 -w0: uma linha, sem header PEM, que é o que a configuração lê.- Sem senha é a escolha honesta: o único lugar para guardá-la seria o mesmo
.envque contém os bytes que ela abre.
Cole o base64 no .env como KEY_RING_PROTECTOR_CERTIFICATE. Depois, coloque protector.pfx em
algum lugar que este host não consiga acessar e remova-o do host:
shred -u protector.key protector.pfx # only after copying the .pfx somewhere safe
Perder o protetor destrói o Cofre de Certificados, e nada o recupera. Nem a Lacuna, nem você. Não há custódia de recuperação (escrow), de propósito. É um arquivo pequeno que nunca muda: guarde-o onde a sua organização guarda aquilo que não pode recriar.
A rotação é aditiva. Veja Operações.
A licença do PKI SDK
Toda implantação precisa dela, com ou sem cofre. Ela é uma configuração, não um arquivo, então vai no
.env, ao lado do Protetor do Chaveiro, como o LacunaPkiLicense.config inteiro codificado em base64
em uma linha:
echo "PKI_LICENSE=$(base64 -w0 LacunaPkiLicense.config)" >> .env
Ela vence, e uma licença vencida não impede o Servidor de iniciar. O Servidor inicia, registra uma
linha Critical citando Certificates:Validation:License, e toda página do console administrativo
mostra um banner dizendo que nada na frota e nada no cofre consegue assinar até que ela seja renovada.
Renová-la é substituir a linha no .env e recriar o container, o que também entrega a licença nova a
cada máquina quando ela se reconecta. Veja
Operações.
Ligando o Cofre de Certificados
VAULT_ENABLED=false é o padrão e a forma mais comum: toda chave privada fica em uma estação de
trabalho. Defina true somente em um Servidor que vá guardar Certificados do Cofre. Ele não precisa de
outra licença além da acima, mas é mais uma coisa que depende dela: com ela vencida, todo Certificado
do Cofre também aparece como UNTRUSTED e uma importação é recusada. Leia antes
Segurança: um PKCS#12 a importar precisa estar no formato PFX
legado.
O REST PKI Core fica desligado até você configurá-lo
Uma Aplicação Cliente pode ter os documentos assinados pelo REST PKI Core em vez do Signer, e só num
Servidor com a seção RestPkiCore definida: as três chaves ou nenhuma, com a chave de API guardada como
segredo, como a do Signer. Acrescente-as ao environment: do Servidor no arquivo compose, com o valor
da chave no .env:
environment:
RestPkiCore__Endpoint: https://core.pki.rest/
RestPkiCore__ApiKey: ${RESTPKI_API_KEY}
RestPkiCore__SecurityContextId: <the security context's id>
Leia antes REST PKI Core: a chave precisa do role Worker, a confiança é do contexto de segurança e não das Autoridades Certificadoras Confiáveis, a política de assinatura e as marcas nas páginas são definidas do lado do REST PKI Core, e este container precisa acessar o endereço do site do REST PKI Core além do endpoint, porque é de lá que um documento assinado é baixado.
4. O schema
Nada a fazer. O Servidor cria e migra o próprio schema na inicialização, nos dois mecanismos. Pule para o passo 5.
Há dois motivos para fazer isso você mesmo: o seu controle de mudanças não concede DDL ao Servidor em
execução, ou um DBA quer ler o SQL antes. Em qualquer um dos casos, defina
Database__MigrateOnStart: "false" no arquivo compose e, antes de cada inicialização em uma versão
nova, aplique as migrações com o .NET SDK e uma cópia do código na versão implantada, ou gere um
script idempotente e aplique-o com o sqlcmd:
dotnet tool restore
dotnet ef migrations script --idempotent --project src/TrustBridge.Server -o schema.sql
sqlcmd -S <host> -U <user> -P <password> -C -I -b -d TrustBridge -i schema.sql
O -I é obrigatório (QUOTED_IDENTIFIER ON; sem ele, o primeiro índice filtrado falha com
Msg 1934), e o -b também (sem ele, uma aplicação parcial informa sucesso). Se você executar
dotnet ef database update em vez disso, acrescente Command Timeout=3600 à connection string: o
padrão da ferramenta é trinta segundos, menos do que leva a criação de um índice sobre uma tabela
AuditEvents grande.
No PostgreSQL, os motivos e o padrão são os mesmos: o usuário trustbridge incluído é superusuário,
então o Servidor cria e migra o próprio banco. Com permissões reduzidas, a identidade precisa de
CREATE no schema public (ou ser dona dele) para aplicar uma migração, e de CREATEDB só se for ela a
criar o banco; uma inicialização recusada cita a que falta. Cada mecanismo tem as próprias migrações,
então informe o mecanismo à ferramenta, ou ela gera o script do SQL Server, que o PostgreSQL recusa
na primeira linha. Aplique o script com psql -v ON_ERROR_STOP=1, que é para o psql o que o -b é
para o sqlcmd: sem ele, o psql continua depois de um comando com falha, termina com 0 e deixa parte
do script aplicada. Não há equivalente ao -I.
dotnet tool restore
dotnet ef migrations script --idempotent --project src/TrustBridge.Server -o schema.sql \
-- --Database:Engine=PostgreSql
docker compose exec -T postgres psql -U trustbridge -d trustbridge -v ON_ERROR_STOP=1 < schema.sql
Isso executa o psql dentro do container do banco incluído, então nenhuma porta precisa ser publicada;
contra um PostgreSQL que você já opera, use
psql -h <host> -U <user> -d trustbridge -v ON_ERROR_STOP=1 -f schema.sql. O banco de dados precisa
existir, e aplicar o script duas vezes é seguro. O dotnet ef database update aceita o mesmo
-- --Database:Engine=PostgreSql no final, com Command Timeout=3600 na connection string.
Com o interruptor desligado, o Servidor se recusa a iniciar quando há uma migração pendente, em vez de iniciar sem ela. Esse é o objetivo do interruptor.
5. Inicie o Servidor
cd /opt/trustbridge && docker compose up -d
docker compose logs -f server
Uma primeira inicialização saudável:
info: TrustBridge.Server.Common.KeyRings.KeyRingPosture[0]
Every key ring this Server keeps in its database is encrypted with the Key Ring Protector
76766DC2741A8DA070CC47E3830D34023D9ED885, and 0 retired protector(s) are kept readable beside
it. Losing a protector makes every key ever sealed under it — and so every Vault Certificate
it wrapped — unrecoverable, so backing them all up, permanently, is part of running this
Server (ADR-0024).
info: TrustBridge.Server.Common.Vault.VaultReadiness[0]
The Certificate Vault is off on this Server, so it holds no certificates of its own. Every
private key it signs with is on a workstation (ADR-0020).
info: TrustBridge.Server.Common.Admin.AdminAccountBootstrap[0]
Created the first admin account admin@example.com in the Administrator role.
info: Now listening on: https://[::]:8443
info: Application started. Press Ctrl+C to shut down.
A primeira linha é a que deve ser lida. Ela informa o thumbprint do protetor que o seu
backup precisa conter e conta os protetores aposentados com que este Servidor ainda consegue ler. Se
você fez uma rotação e ela diz 0 retired protector(s), o protetor antigo não está configurado e
tudo o que foi selado sob ele está ilegível.
Com o cofre ligado, a segunda linha passa a ser The Certificate Vault is open: its key ring holds N
key(s)…. Um warn no lugar dela dizendo que o cofre está closed until they have run significa que
o Servidor subiu antes do banco de dados. Essa é a trava do cofre.
Atrás de um proxy reverso, o Servidor escuta em http://[::]:8443, e uma linha que começa com
Forwarded headers: cita os proxies em que ele confia.
No PostgreSQL, a primeira inicialização de um banco vazio registra várias linhas fail antes de
aplicar qualquer coisa, e elas não são uma falha. Cada uma é o provider do banco lendo o histórico de
migrações antes de a tabela existir:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (10ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "MigrationId", "ProductVersion"
FROM "__EFMigrationsHistory"
ORDER BY "MigrationId";
A linha que resolve a questão vem depois delas: uma linha SchemaUpgrade dizendo quais migrações foram
aplicadas. Toda inicialização seguinte diz The database holds every migration this Server was built
with e não registra nenhum fail.
6. Verifique se ele realmente funciona
curl -s https://trustbridge.example.com/healthz # Healthy
curl -o /dev/null -w '%{http_code}\n' https://trustbridge.example.com/ # 200 front door
curl -o /dev/null -w '%{http_code}\n' https://trustbridge.example.com/admin # 302 to login
curl -o /dev/null -w '%{http_code}\n' https://trustbridge.example.com/api/v1/sessions # 401 no API key
Em seguida, entre em /admin com a conta inicial. Isso lê o banco de dados pelo caminho que uma
requisição real percorre, e não pelo de um probe. Defina uma senha real, cadastre o segundo fator e
então remova ADMIN_BOOTSTRAP_EMAIL e ADMIN_BOOTSTRAP_PASSWORD do .env e recrie o container.
Elas não têm efeito depois da primeira inicialização; removê-las é uma questão de higiene.
A conta inicial entra com senha, então deixe Admin:CertificateSignIn:Required desligado na primeira
inicialização. Se você pretende desligar as senhas depois, crie antes um segundo Administrador: um
CPF vinculado é sempre definido por outro Administrador, nunca pelo dono da conta, e uma conta sem
um não consegue entrar quando as senhas estão desligadas. Veja
Admin:CertificateSignIn:Required.
Por fim, registre uma Aplicação Cliente em /admin/applications, emita uma chave de API e exercite a
API REST uma vez, de ponta a ponta.
Backups
| O quê | Como | Se você perdê-lo |
|---|---|---|
| O banco de dados | BACKUP DATABASE, ou os backups de SQL Server que você já faz. No PostgreSQL, pg_dump (abaixo), ou os backups de PostgreSQL que você já faz | Sessões, Trilha de Auditoria, Registro de Certificados, Versões da Estação de Trabalho, os dois chaveiros e o texto cifrado do cofre |
O .pfx do Protetor do Chaveiro | Copie o arquivo uma vez. Ele nunca muda | Nenhum Certificado do Cofre pode mais ser aberto, permanentemente, e todos os administradores entram de novo |
O .pfx de cada protetor aposentado | Da mesma forma, guardado para sempre | Tudo o que foi selado sob aquele protetor, permanentemente |
/etc/trustbridge/tls | Onde quer que os seus certificados fiquem | Emita de novo |
| O log do container, para onde quer que o seu logging driver o envie | A sua retenção de logs, não uma cópia de arquivo | Linhas de auditoria que o banco de dados não aceitou. A trilha tem um buraco exatamente onde o banco esteve fora do ar |
O logging driver json-file do arquivo compose guarda as linhas no diretório do próprio container, e
o Docker as apaga junto com o container; então docker compose down e a atualização descrita
abaixo as descartam. Aponte o driver de logging: para algo fora desta máquina, ou execute um agente
de log sobre os arquivos, antes de precisar deles.
No PostgreSQL, o backup do banco incluído é feito com pg_dump, e não copiando o volume. Os
arquivos do volume são de um servidor em execução, e uma cópia feita por baixo dele não é um backup. O
pg_dump já está dentro do container do banco e grava um snapshot consistente enquanto o Servidor
continua funcionando; o formato personalizado (-Fc) é compactado e é o que o pg_restore lê:
cd /opt/trustbridge
docker compose exec -T postgres pg_dump -U trustbridge -Fc trustbridge > trustbridge-$(date +%F).dump
Restaure num banco vazio (o volume de uma implantação nova, ou um banco que você excluiu e criou de novo) com o Servidor parado, para que nada grave enquanto a restauração está pela metade:
docker compose stop server
docker compose exec -T postgres pg_restore -U trustbridge -d trustbridge --clean --if-exists \
< trustbridge-2026-10-20.dump
docker compose start server
O backup de um mecanismo não é restaurado no outro. Copie os dumps para fora deste host, como o resto
da tabela. Uma implantação feita com o deploy-docker.ps1 também guarda o diretório da implantação,
cujo .env é a única cópia do Protetor do Chaveiro e da chave de criptografia do REST PKI Core.
Restaurar exige que duas coisas cheguem juntas. Um banco de dados sem o protetor dele impede a inicialização; um protetor sem o banco de dados inteiro (linhas do cofre presentes, linhas do chaveiro ausentes) é recusado pelo motivo simétrico.
Atualizações
Faça antes um backup do banco de dados. Esse backup é o seu caminho de volta (abaixo).
docker compose pull && docker compose up -d
A imagem nova aplica as migrações de que precisa ao subir e registra os nomes delas no log. Se você
definiu Database__MigrateOnStart: "false", aplique as migrações antes; a imagem nova se recusa
a iniciar contra um schema atrasado. Uma implantação feita com o deploy-docker.ps1 é atualizada
executando-o de novo com -Image trustbridge-server:<version>.
No PostgreSQL, o pull leva o postgres:16 à última versão secundária da 16, e nunca à 17. Uma
versão principal nova é um formato de dados novo: apontado para um volume da 16, o postgres:17 se
recusa a iniciar. Levar o banco incluído a uma versão principal posterior é um pg_dump da 16 e um
pg_restore num volume vazio da 17, como em Backups, e o momento é você quem escolhe: o
Servidor só precisa da 16 ou posterior.
Conte com uma indisponibilidade visível de um ou dois minutos: uma instância significa que não há rolling restart. As estações de trabalho se reconectam sozinhas, e as sessões vinculadas a estações de trabalho sobrevivem intactas. As sessões vinculadas a um Certificado do Cofre, não; elas terminam como Interrompida e os clientes criam novas.
Fazer rollback é restaurar esse backup, não trocar a tag. A maioria das releases do Servidor desde a 1.9.0 traz migrações (o Changelog diz quais; a 1.14.1, a 1.16.0, a 1.19.0, a 1.22.0 e a 1.24.0 não), e uma imagem mais antiga se recusa a iniciar contra um banco de dados que uma mais nova migrou. Para voltar, restaure o backup anterior à atualização e implante a tag anterior contra ele. Voltar de uma release sem migração para a anterior a ela é a exceção, e é só trocar a tag: da 1.24.0 para a 1.23.0, por exemplo.
Atualizando para a 1.17.0 ou posterior com Sessions:SignatureAllowance definida: a chave não é
mais lida, então as sessões da implantação ficam sem limite até um Administrador definir a Franquia de
Assinaturas em cada Aplicação Cliente que precisa de uma, na tela Aplicações Cliente. Faça isso
logo depois da atualização (1 para um integrador que pediu sessões de uso único) e então remova a
chave. O Servidor registra um Warning a cada inicialização enquanto ela estiver definida. Veja
Sessions:SignatureAllowance.
Atualizando para a 1.18.0 ou posterior com Signing:StrandedAfter definida: ela agora precisa ser
maior que o dobro de Signing:RequestBudget mais Signing:SignerCancelBudget, ou o Servidor se recusa
a iniciar, citando a chave. Os padrões atendem à regra.
Atualizando para a 1.20.0 ou posterior no SQL Server: nada muda até você pedir; Database:Engine
tem o padrão SqlServer. Uma das migrações dessa release passa para maiúsculas os thumbprints que o
banco guarda. Num banco criado com uma collation que diferencia maiúsculas de minúsculas, ela se recusa,
sem mudar nada, se duas linhas diferirem só nisso, e cita a tabela.
Atualizando para a 1.23.0 ou posterior: três migrações rodam na inicialização, nos dois bancos.
ReportWorkstationConsole acrescenta sete colunas anuláveis a Workstations, e
RecordTheRunsFailureCause, uma a SigningOperations; as duas são rápidas.
IndexTheAuditLogByWorkstationAndTime cria um índice na Trilha de Auditoria, e a duração dela cresce
com a Trilha de Auditoria. No SQL Server, atualize para a 1.24.0, e não para a 1.23.0, quando a Trilha
de Auditoria for grande. A 1.23.0 cria esse índice offline, e uma criação offline bloqueia as
gravações na Trilha de Auditoria durante quase todo o tempo, então linhas da Trilha de Auditoria
gravadas nesse intervalo podem estourar o timeout e se perder. A 1.24.0 cria o mesmo índice online onde
a edição permite: com prioridade baixa no Azure SQL Database, no Azure SQL Managed Instance e no
Enterprise ou Developer 2022 ou posterior, de modo que a criação espera atrás das gravações, e não na
frente delas; online simples no Enterprise anterior; e ainda offline no Standard, no Express e no Web,
que não criam índices online, então nessas edições agende a atualização para um horário tranquilo. O
PostgreSQL cria o índice do mesmo jeito nas duas releases, com uma criação simples que também segura as
gravações enquanto roda, então uma Trilha de Auditoria grande ali também pede um horário tranquilo. Um
banco que já aplicou a migração na 1.23.0 não precisa de mais nada da 1.24.0. Se um comando de migração
passar do próprio limite de tempo, veja
Database:MigrateOnStart.
Vindo do Servidor 1.6.x, defina PKI_LICENSE no .env antes de atualizar: desde a 1.7.0 toda
implantação precisa dela, e o arquivo compose não inicia o Servidor sem ela. Atrás de um proxy reverso
configurado com ASPNETCORE_FORWARDEDHEADERS_ENABLED, passe a usar ForwardedHeaders:TrustedProxies
como no passo 2.
Atualizar para a 1.15.0 ou posterior move o protocolo das estações de trabalho para o 6. Um Servidor aceita o protocolo atual das estações de trabalho e o anterior, então a janela dele é 5–6:
| Uma máquina com | Contra o Servidor 1.15.0 ou posterior |
|---|---|
| Workstation 1.6.0 a 1.10.0 (protocolo 6) | Funciona. Ela não consegue se conectar a um Servidor anterior à 1.15.0. |
| Workstation 1.5.0 (protocolo 5) | Funciona e atende toda sessão, mas nunca é escolhida para uma sessão cuja Aplicação Cliente tem o PIN por transação ligado. |
| Workstation 1.4.x ou anterior | Recusada no handshake, com Atualização necessária. Ela recebe uma versão atual manualmente ou por Política de Grupo. |
Por isso, antes de atualizar, passe para o Workstation 1.5.0 toda máquina que ainda estiver na 1.4.x
(a coluna Versão do serviço da tela Estações de Trabalho mostra quais). Depois, atualize o
Servidor, faça upload de TrustBridge-Workstation-1.10.0-x64.msi na tela Versões da Estação de
Trabalho e ofereça-o à frota na tela Estações de Trabalho. As máquinas com atualizações ligadas o
instalam sozinhas; nas demais, você precisa instalar o MSI. Entregue o Workstation 1.8.0 e
posteriores com o Servidor 1.20.0 ou posterior, o primeiro Servidor que aceita o Relatório da Estação
de Trabalho deles. Um Servidor anterior fecha a conexão de uma máquina no 1.8.0 cujo relatório passa de
32 KB, o que uma máquina com muitos tokens, middlewares ou erros pode enviar; a partir do 1.8.1 a
máquina mantém os relatórios abaixo de 32 KB até o Servidor aceitar um, e continua conectada. O console que o Workstation 1.10.0
informa aparece na tela Estações de Trabalho a partir do Servidor 1.23.0. Só ligue o PIN por transação de uma Aplicação Cliente quando
as máquinas dos titulares dela rodarem a 1.6.0 ou posterior. Veja
Atualizar um Servidor e sua frota para o protocolo 6
e Atualizações entregues pelo Servidor.
Atualizando a partir do Servidor 1.14.x com a Assinatura de teste ligada: remova as chaves
TestSignature: que não são mais lidas e revogue a chave que TestSignature:ApiKey guardava (veja
A Assinatura de teste).
Um banco de dados criado pelo Servidor 1.2.0 ou anterior não pode ser atualizado: o histórico de migrações foi consolidado em uma única linha de base depois disso, e um banco assim é recusado na inicialização como um que tem migrações que este build não contém. Descarte-o e deixe o Servidor criar um novo.
Quando o Servidor não inicia
| O log diz | Causa |
|---|---|
KeyRingProtector:Certificate is not configured | Não há protetor. Toda implantação precisa de um. |
Certificates:Validation:License is not configured | Não há licença do PKI SDK. Toda implantação precisa de uma, com ou sem cofre. |
set PKI_LICENSE in .env (do docker compose, antes de o container iniciar) | PKI_LICENSE está vazia no .env. A mesma mensagem cita TLS_CERT_PASSWORD ou KEY_RING_PROTECTOR_CERTIFICATE quando essas estão vazias. |
… is not base64, so it cannot be a PKCS#12 | O valor tem um header PEM ou quebras de linha. Use base64 -w0. |
… could not be opened as a PKCS#12 | CertificatePassword errada, ou os bytes não são um PKCS#12. O Servidor não consegue distinguir os dois casos. |
… carries no RSA private key this process can use | Um certificado ECDSA, ou um exportado sem a chave. |
The Certificate Vault's key ring holds N key(s) and the Key Ring Protector configured here opens none of them | Um protetor trocado. Recoloque o antigo, ou adicione-o em RetiredCertificates. |
The Certificate Vault holds N certificate(s) and its key ring is empty | Uma restauração parcial. Restaure o banco de dados inteiro. |
Signer:ApiKey is required when Signer:BaseAddress is configured | Signer configurado pela metade. Defina os dois ou nenhum. |
RestPkiCore:… is required when any RestPkiCore key is set | REST PKI Core configurado pela metade. Defina as três ou nenhuma. |
RestPkiCore:Endpoint must be an https address / must end in a slash | Escreva o endpoint como https://…/. |
Database:Engine is PostgreSql, but ConnectionStrings:TrustBridge is written for SQL Server | O mecanismo e a connection string não combinam, em geral porque Database__Engine foi definida e a string do SQL Server foi mantida. O caso inverso cita o PostgreSQL. Uma implantação não muda de mecanismo; corrija o que estiver errado. |
Database:Engine is '…', which names no engine this Server runs on | Um erro de digitação. SqlServer ou PostgreSql, sem diferenciar maiúsculas de minúsculas. |
Signing:StrandedAfter must be greater than twice Signing:RequestBudget | Um StrandedAfter abaixo do piso que a 1.18.0 elevou. Aumente-o, ou remova-o para usar o padrão. |
Dashboard:TimeZone names '…' | Um fuso que o Servidor não conhece, ou um id de fuso do Windows. Escreva o fuso IANA, como America/Sao_Paulo. |
ForwardedHeaders:TrustedProxies:N is '…' | Uma entrada que não é um endereço ou uma rede simples, que confia em todos os endereços (/0), que tem bits definidos além do prefixo, ou que é IPv4 mapeado ou link-local. Escreva o endereço do próprio proxy, como o Servidor o mostra. |
ForwardedHeaders:TrustedProxies is the one value '…' | A lista informada como um valor único. Use índice: ForwardedHeaders__TrustedProxies__0. |
ForwardedHeaders:TrustedProxies is set, and so is ASPNETCORE_FORWARDEDHEADERS_ENABLED | Os dois definidos. Fora do App Service, remova a variável. |
Admin:CertificateSignIn:Enabled is on, but Admin:CertificateSignIn:WebPkiLicense is not set | O Acesso com Certificado precisa da licença do Web PKI, no formato de uma linha. |
Admin:CertificateSignIn:Required is on, which switches passwords off, but Admin:CertificateSignIn:Enabled is off | Um console sem nenhuma forma de entrar. Ligue Enabled, ou desligue Required. |
Workstations:ClientTimeout of … is shorter than / is longer than | Fora do intervalo de 10 segundos a 5 minutos. Escreva como hh:mm:ss: um 15 sozinho são quinze dias. |
Language:DefaultCulture recusado | Um idioma padrão fora do conjunto suportado. |
… is behind the build pointed at it by N migration(s) | MigrateOnStart está desligado e o schema precisa dessas migrações. |
… has N migration(s) applied that this build does not contain | Uma imagem mais antiga contra um schema mais novo (restaure o backup anterior à atualização), ou um banco de dados da 1.2.0 ou anterior. |
This Server's database did not answer in N seconds | O banco de dados nunca respondeu. Verifique a connection string e se o SQL Server (ou o PostgreSQL) está no ar. |
… could not apply N pending migration(s) | Em geral, falta de permissão de DDL para a identidade que se conecta: db_ddladmin no SQL Server, CREATE no schema public no PostgreSQL. |
… needs the CREATEDB attribute | PostgreSQL: o banco não existe e a identidade não pode criá-lo. Conceda CREATEDB, ou peça a um DBA que crie o banco vazio. |
… ran out of time applying N pending migration(s) | Um comando de migração passou de trinta minutos. Nada ficou aplicado pela metade. |
… database stopped answering while it applied N pending migration(s) | A conexão caiu durante a migração. Verifique se o banco de dados está no ar e se nada o reiniciou. |
| Inicia, mas toda página do console mostra um banner de licença | A licença do PKI SDK venceu ou foi recusada. O Servidor funciona, e nada consegue assinar até que ela seja renovada. |
| Inicia, mas as estações de trabalho nunca se conectam | As estações de trabalho não confiam na cadeia TLS, ou as máquinas executam uma versão da Estação de Trabalho que este Servidor recusa (Atualização necessária nas linhas delas na frota). |
Duas linhas do framework parecem falhas do protetor e não são: KeyRingProvider[48] An error occurred while reading the key ring e XmlKeyManager[24] … Unable to retrieve the decryption key.
Em um Servidor que iniciou sem erros, elas significam que o banco de dados está inacessível. As
recusas do próprio protetor sempre citam KeyRingProtector: e param o host.