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, 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 | 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, o arquivo compose
(docker-compose.yml), um exemplo de ambiente comentado (.env.example) e os modelos de Diretiva de
Grupo para a frota. As instruções abaixo supõem que você tem esse pacote no host de destino.
Antes de escolher a forma de hospedagem
Quatro 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.
O Servidor atualiza o próprio esquema. 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) 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 esquema mais novo responderia errado em vez de falhar. |
| Inacessível | Aguarda cerca de noventa segundos e então se recusa a iniciar. |
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.
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. 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.
- 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. - A licença do Lacuna PKI SDK, somente se você for ligar o Cofre de Certificados. A imagem não traz licença.
- Um SQL Server. O arquivo compose pode executar um ao lado do Servidor, mas leia a observação sobre a edição dele.
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, com os caches do PKI SDK de emissores e LCRs baixados (/tmp/trustbridge-pki) e o próprio
arquivo de log rotativo (/tmp/trustbridge-logs). Os dois são caches de algo que já está registrado
em outro lugar, e perdê-los não custa nada além de latência e das linhas da tela Log do Servidor.
Nada ali precisa de backup.
2. TLS
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.
Se preferir terminar o TLS em um proxy reverso que você já opera, aponte-o para a porta 8443 do
container e remova as variáveis Kestrel__Certificates__* do arquivo compose, mas então defina também
ASPNETCORE_FORWARDEDHEADERS_ENABLED=true, ou toda linha de auditoria vai registrar o endereço do seu
proxy como o de quem chamou.
3. Configuração
cd /opt/trustbridge
cp <package>/docker-compose.yml .
cp <package>/.env.example .env
chmod 600 .env
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. Nunca latest. |
MSSQL_SA_PASSWORD | A senha do sa do SQL Server incluído (8 ou mais caracteres de três classes de caracteres). |
TLS_CERT_PASSWORD | A senha de /etc/trustbridge/tls/server.pfx. |
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. |
VAULT_ENABLED | false, a menos que este Servidor vá guardar Certificados do Cofre. |
PKI_LICENSE | A licença do Lacuna PKI SDK, em base64 em uma linha. Necessária com o cofre ligado. |
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 da primeira entrada. |
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ê.
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 uma impressão digital (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 cabeçalho 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.
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, e defina
PKI_LICENSE junto. Leia antes Segurança: com o recurso
ligado, a licença do PKI SDK passa a ser crítica, e um PKCS#12 a importar precisa estar no formato PFX
legado.
echo "PKI_LICENSE=$(base64 -w0 LacunaPkiLicense.config)" >> .env
4. O esquema
Nada a fazer. O Servidor cria e migra o próprio esquema na inicialização. 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.
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 a impressão digital 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.
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.
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 | Sessões, Trilha de Auditoria, Registro de Certificados, 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.
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
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 esquema atrasado.
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.
Atualize primeiro o Servidor, depois as estações de trabalho, dentro de um mesmo ciclo de versão. Um Servidor aceita o protocolo atual das estações de trabalho e o anterior; uma estação de trabalho à frente do Servidor não consegue se conectar. Veja Operações.
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. |
… is not base64, so it cannot be a PKCS#12 | O valor tem um cabeçalho 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. |
… is behind the build pointed at it by N migration(s) | MigrateOnStart está desligado e o esquema precisa dessas migrações. |
… has N migration(s) applied that this build does not contain | Uma imagem mais antiga contra um esquema mais novo, ou um banco de dados da 1.2.0 ou anterior. |
… could not be reached in 90 seconds | O banco de dados nunca respondeu. Verifique a connection string e se o SQL Server está no ar. |
… could not apply N pending migration(s) | Em geral, falta de permissão de DDL para a identidade que se conecta. |
… ran out of time applying N pending migration(s) | Um comando de migração passou de trinta minutos. Nada ficou aplicado pela metade. |
| Inicia, mas as estações de trabalho nunca se conectam | As estações de trabalho não confiam na cadeia TLS. |
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.