Ir para o conteúdo

Segredos

Nomes entram. Valores nunca saem.

Nenhum endpoint de API retorna um valor de segredo armazenado. Nenhuma tela oferece um botão "mostrar". Quem perdeu um valor o substitui — essa é a mesma chamada que o criou, pelo mesmo formulário. Isto não é uma decisão de política: o caminho de leitura simplesmente não existe no código. (REQ-1558)


Sintaxe de referência

Três formas de referência são válidas onde quer que o Provisa resolva credenciais:

Forma Resolve a partir de Quem pode usar
${env:VAR_NAME} O ambiente do processo do servidor Somente configuração de implantação
${secret:NAME} O cofre da organização — compartilhado por todos os membros Qualquer campo que aceite uma referência de credencial
${user:NAME} O cofre pessoal de quem está agindo Qualquer campo que aceite uma referência de credencial

Em uma implantação multi-tenant, mantenha as credenciais da plataforma em um serviço de segredos, e não em variáveis de ambiente. Um administrador de tenant pode escrever ${env:...} em um campo de credencial próprio, então qualquer valor no ambiente do processo fica ao alcance de todos os tenants. Deixe no ambiente apenas a credencial própria do backend de segredos.

A resolução é de falha fechada de ponta a ponta. Um nome de provedor desconhecido, um nome não definido e um backend inacessível levantam erro. Uma referência que não pôde ser resolvida nunca é silenciosamente substituída por uma string vazia. (REQ-1557) [tool-verified: provisa/core/secrets.py:92-117]

Formato do nome

Nomes de segredo devem corresponder a [A-Za-z_][A-Za-z0-9_]* — letras, dígitos e sublinhados, começando por letra ou sublinhado. A restrição é prática: ${secret:NAME} é analisado pela gramática de referência, que lê até o } de fechamento. Um nome contendo chave, espaço ou dois-pontos produziria uma referência que é analisada como outra coisa. [tool-verified: provisa/core/secrets_store.py:61]


Dois cofres, um serviço

Toda organização tem dois cofres. Ambos vivem dentro do mesmo serviço de segredos. (REQ-1560)

Cofre da organização — A credencial que um administrador da organização guarda aqui é compartilhada. Todo membro que referencia ${secret:DATABASE_TOKEN} recebe o mesmo valor. Isto é para credenciais que a organização possui: uma senha de banco de dados compartilhada, uma chave de conta de serviço, um token de implantação. O cofre da organização exige a capacidade org_settings para leitura ou escrita.

Cofre pessoal — Uma credencial guardada aqui pertence a exatamente uma pessoa. Quando duas pessoas mantêm cada uma um GIT_TOKEN, ${user:GIT_TOKEN} resolve para o de quem estiver agindo. O mesmo texto de referência entrega a cada pessoa a sua própria credencial. Quem não guardou nada recebe um erro, não o valor de outra pessoa. Nenhuma capacidade controla o cofre pessoal — guardar a sua própria credencial não é um privilégio que um administrador concede. E não há sintaxe de requisição para nomear o cofre de outra pessoa. [tool-verified: provisa/api/admin/secrets_router.py:86-103]

O escopo faz parte da referência, não é uma permissão em torno dela. ${secret:NAME} e ${user:NAME} nunca respondem um pelo outro.


Escolhendo um serviço de segredos

Administração → Segurança → Serviço de segredos. O painel é visível a quem detém a capacidade platform_settings. Todo backend que o build conhece é listado, esteja o SDK instalado ou não. Uma linha esmaecida diz qual pacote Python está faltando — o painel o nomeia em vez de esconder a opção por completo.

Cinco backends acompanham o produto:

Chave Rótulo Requer
provisa Provisa (built-in, encrypted) Nada; este é o padrão
hashicorp_vault HashiCorp Vault (KV v2) hvac
aws_secrets_manager AWS Secrets Manager boto3
gcp_secret_manager Google Secret Manager google-cloud-secret-manager
azure_key_vault Azure Key Vault (secrets) azure-keyvault-secrets

[tool-verified: provisa/core/secrets_registry.py:161-299]

A seleção é de falha fechada: um backend desconhecido ou indisponível levanta erro na inicialização em vez de recair silentemente em outro. (REQ-1557)

A credencial do próprio backend

A credencial de conexão de um backend central é configuração de processo. Ela vem de ${env:...} apenas — nunca de ${secret:...}. Um serviço de segredos cuja própria credencial vive dentro dele mesmo não pode ser aberto, então a cadeia de confiança termina no ambiente do host por design. O registro impõe isto: qualquer valor de configuração na especificação de um backend é resolvido com providers=("env",) antes de o backend ser construído. [tool-verified: provisa/core/secrets_registry.py:128-141]

Exemplo — configuração do Vault em provisa.yaml:

secrets:
  provider: hashicorp_vault
  hashicorp_vault:
    url: https://vault.internal:8200
    token: ${env:VAULT_TOKEN}   # process env only — never ${secret:...}
    mount: secret

Serviço central vs. built-in

Quando um serviço central está configurado, o Provisa lê dele mas não escreve nele. O serviço central é dono da criação e da exclusão de entradas — essas operações pertencem à ferramenta dele. A página de Segredos diz isso e não oferece botão de criação. (REQ-1557)

Quando o backend provisa built-in está ativo, a página de Segredos é totalmente gravável: criar, substituir e excluir pela UI ou via API.


Os segredos de uma organização em um serviço central

Uma implantação que hospeda muitas organizações e lê um serviço central o lê, para cada organização, dentro do prefixo próprio dessa organização. Uma referência continua sendo o nome curto (${secret:db#password}); o prefixo é adicionado na leitura. (REQ-1557) [tool-verified: provisa/core/secrets_providers.py, org_namespace]

Serviço Onde fica o segredo db da organização acme
HashiCorp Vault (KV v2) orgs/acme/db
AWS Secrets Manager orgs/acme/db
GCP Secret Manager org-acme--db
Azure Key Vault o4-acme-db (o, o comprimento do id da organização, o id com _ escrito como -, depois o nome)

Uma referência é recusada, pelo nome, quando:

  • seu nome sairia do prefixo: começa com /, contém um segmento .. ou uma barra invertida, ou contém / no GCP ou no Azure, cujos nomes são planos;
  • seu nome é longo demais para o serviço depois de prefixado (GCP 255 caracteres, Azure 127); isso é recusado quando a referência é salva, com o limite informado;
  • nenhuma organização está vinculada à operação que a lê.

Uma implantação de uma única organização lê uma referência exatamente como está escrita, sem prefixo: o serviço ao qual está ligada é o daquela organização, e nada precisa ser movido.

Passar para o prefixo. Em uma implantação de muitas organizações, cada segredo que uma organização referencia precisa estar sob o prefixo dessa organização. Copie cada um para lá com as ferramentas próprias do serviço e depois encurte a referência para o nome dentro do prefixo (${secret:teams/data/db}, guardado em orgs/acme/db, passa a ser ${secret:db}). Uma referência cujo segredo não foi movido falha como não encontrada; ela não é lida de onde estava.

O armazenamento built-in do Provisa

O padrão quando nenhum serviço central está configurado. Cada linha em secrets_store guarda um blob de envelope criptografado — a coluna value é binária, não texto, e a chave de descriptografia vive no ambiente do processo, não no banco de dados. Uma cópia do plano de controle sem a chave mestra da implantação guarda texto cifrado e nada mais. (REQ-1558)

A criptografia nunca é opcional. Quando nenhuma chave de criptografia de processo está configurada, o armazenamento recorre a um keychain local. Se o host não tiver keychain para guardar uma chave, o armazenamento se recusa a escrever em vez de guardar o valor às claras. [tool-verified: provisa/core/secrets_store.py:130-159]

Formato de armazenamento [tool-verified: provisa/core/schema_admin.py:493-505]:

Coluna Tipo Finalidade
org_id Text A organização dona deste segredo
owner_id Text "*" para o cofre da organização; id de usuário para o cofre pessoal
name Text O nome da referência
value LargeBinary Blob de envelope criptografado
description Text Para que serve o segredo — nunca derivado do valor
updated_by Text Quem o definiu por último

Uma organização que criou a própria chave de criptografia tem seus segredos envolvidos por essa chave; caso contrário, são envolvidos pela chave da implantação. Veja Criptografia em repouso.

A coluna value não é selecionada em nenhuma consulta de listagem. [tool-verified: provisa/core/secrets_store.py:214-235]


Endpoints da API

Todas as rotas ficam sob /admin/orgs/{org_id}. O cofre da organização exige org_settings naquela organização. O cofre pessoal não exige capacidade alguma — o dono é lido da identidade autenticada; não há parâmetro de requisição para nomear o cofre de outra pessoa.

Método Caminho O que faz
GET /secrets Lista nomes e referências do cofre da organização
PUT /secrets/{name} Cria ou substitui um segredo da organização
DELETE /secrets/{name} Exclui um segredo da organização
GET /my-secrets Lista os nomes e referências pessoais de quem chama
PUT /my-secrets/{name} Cria ou substitui um segredo de quem chama
DELETE /my-secrets/{name} Exclui um segredo de quem chama

Toda resposta devolve metadados — nome, descrição, updated_at, updated_by e a string de reference para colar — mas nunca o valor. O corpo do PUT carrega value (obrigatório) e description (opcional). Uma substituição é a mesma chamada que uma criação: o nome é a identidade, não um ID à parte.

Toda escrita é registrada no log de auditoria. A entrada do log nomeia o ator e o nome do segredo. O valor não é registrado, nem mesmo o seu tamanho. [tool-verified: provisa/api/admin/secrets_router.py:106-117]


Onde ${secret:NAME} é resolvido

A resolução acontece dentro de uma operação vinculada a um contexto, não no momento do import nem na inicialização. O armazenamento lê e descriptografa os segredos da organização uma vez no início dessa operação e mantém o mapa em um ContextVar durante a sua duração. Fora de uma operação vinculada, ${secret:NAME} levanta erro. (REQ-1557) [tool-verified: provisa/core/secrets_store.py:269-290]

Dois pontos de chamada estabelecem o vínculo:

Operações de remote git. Quando a URL do remote do repositório de uma organização contém uma referência ${secret:...} ou ${user:...} — por exemplo, um token de push embutido na URL — o roteador de ambientes vincula tanto o cofre da organização quanto o cofre pessoal do usuário que está agindo em torno da chamada git. A forma ${user:GIT_TOKEN} significa que um commit chega sob a credencial de quem o enviou, não de uma conta de serviço compartilhada. [tool-verified: provisa/api/admin/environments_router.py:1263]

Leituras da chave de API do fornecedor de IA. Quando o Provisa lê a chave do fornecedor de LLM de uma organização e essa chave está guardada como referência ${secret:NAME}, bound_to_request_org estabelece o cofre da organização para aquela requisição. A referência é resolvida na saída; o próprio texto da referência nunca é enviado ao fornecedor. (REQ-1580) [tool-verified: provisa/core/org_secrets.py:76-79]


Chaves de fornecedor de IA da organização como referências de segredo

A chave de fornecedor de IA de uma organização (Anthropic, OpenAI e outros) pode ser guardada como referência ${secret:NAME} em vez de uma chave literal. (REQ-1580)

Guarde a chave no cofre da organização primeiro:

PUT /admin/orgs/{org_id}/secrets/OPENAI_KEY
{ "value": "sk-...", "description": "OpenAI production key" }

Depois configure a IA da organização para referenciá-la:

vendor key field → ${secret:OPENAI_KEY}

A referência é guardada criptografada em org_secrets. No momento da consulta o Provisa resolve ${secret:OPENAI_KEY} contra o cofre da organização e entrega a chave literal ao SDK do fornecedor. Girar a entrada do cofre tem efeito imediato — nenhuma mudança de configuração do lado das configurações da organização. [tool-verified: provisa/core/org_secrets.py:64-79]


As credenciais dos stores da própria implantação

As senhas dos stores que o Provisa executa (PostgreSQL, Redis, MinIO, a conta de administrador do Grafana e o segredo compartilhado do cluster Trino) não são segredos em nenhum dos dois cofres. Elas são geradas por implantação e mantidas pela implantação: em ~/.provisa/store-credentials.env (modo 600), no estado do Terraform, ou no Secret do Kubernetes que credentials.existingSecret do chart Helm nomeia. Veja Fortalecimento da implantação.


Acesso do administrador da plataforma

Um administrador da plataforma operando o plano de controle não tem leitura de nenhum valor de segredo de nenhuma organização. A guarda org_settings recusa explicitamente cross_org e o bypass de plataforma: administrar o ciclo de vida de uma organização não é uma leitura das credenciais que aquela organização guarda. O servidor impõe isto independentemente da UI. (REQ-1361) [tool-verified: provisa/api/admin/secrets_router.py:53-83]


Veja também