Segredo
Quando usar: quando o serviço precisa de uma credencial ou de uma chave que não pode ser lida por quem só consulta a configuração: chave de assinatura, token de terceiro, credencial de banco.
O valor nunca está no repositório, nunca é montado pelo módulo e nunca é impresso. O serviço declara o slot; a plataforma provisiona o conteúdo e o entrega montado como arquivo.
Declarar o slot
nns/service.yaml:
secrets:
- slot: db-runtime
description: "Conta de runtime: username, password, database, ca.crt. Só a DML concedida por tabela pela migração."
- slot: db-migrator
description: "Conta de migração: username, password, database, ca.crt, runtime_account. Só no Job de migração."
- slot: notifier-signature
source: generator:random
description: "Chave HMAC com que o notificador confere a assinatura dos avisos enviados."
db-runtimeedb-migratorsão do banco: existem só quandodatabaseestá declarado, e o conteúdo deles é a credencial (a de migração é a única com DDL).- Qualquer outro slot declara a
source:generator:random— a plataforma gera um valor aleatório (chave de assinatura, por exemplo);generator:ed25519-seed— a plataforma gera uma seed Ed25519;external— valor de terceiro, entregue uma vez pelo operador;cloud-tenant-credential— credencial de nuvem por tenant (só para módulo de nuvem).
- No máximo 16 slots,
slotem^[a-z][a-z0-9-]{0,30}$.
Ler
O conteúdo chega como um diretório por slot, um arquivo por chave, em NN_SECRETS_DIR
(/var/run/nnumbers/secrets no pod). A leitura é por NnsConfig.readSecret(slot, chave):
/** O slot do Secret com a chave da assinatura (`service.yaml`, `secrets[].slot`). */
static final String SIGNATURE_SLOT = "notifier-signature";
/** A chave dentro do slot (um arquivo por chave, dentro do diretório do slot). */
static final String SIGNATURE_KEY = "value";
/** A assinatura do corpo com a chave do slot: `HMAC-SHA256`, em base64url sem preenchimento. */
String sign(byte[] payload) {
byte[] key = config.readSecret(SIGNATURE_SLOT, SIGNATURE_KEY);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return Base64.getUrlEncoder().withoutPadding().encodeToString(mac.doFinal(payload));
} catch (java.security.GeneralSecurityException e) {
throw new IllegalStateException("sample: assinatura indisponível", e);
} finally {
java.util.Arrays.fill(key, (byte) 0); // o valor não fica na memória além do uso
}
}
O que o SDK já garante na leitura:
- o caminho é montado como
<NN_SECRETS_DIR>/<slot>/<chave>, com slot e chave validados — não há como escapar do diretório; - arquivo maior que 64 KiB, permissão mais aberta que
0640(ou0644de bico de pena), arquivo ausente e arquivo ilegível recusam, cada um com a sua mensagem — e a mensagem cita slot e chave, nunca o conteúdo; - o valor é
byte[]: quem usa decide como interpretar (texto, PEM, chave HMAC).
Como testar
O teste escreve o diretório do slot num diretório temporário e aponta NN_SECRETS_DIR para lá:
Path slot = dir.resolve("secrets").resolve(NotifierClient.SIGNATURE_SLOT);
Files.createDirectories(slot);
Path value = slot.resolve(NotifierClient.SIGNATURE_KEY);
Files.writeString(value, SECRET);
Files.setPosixFilePermissions(value, PosixFilePermissions.fromString("rw-------"));
config =
NnsConfig.fromEnviron(
Map.of(
"NN_ENV", "development",
"NN_SERVICE_ID", "sample",
"NN_MODULE_ID", "sample",
"NN_VERSION", "0.1.0",
"NN_SECRETS_DIR", dir.resolve("secrets").toString()));
E o efeito é conferido no que sai — a assinatura é recalculada de forma independente:
@Test
void theBodyIsSignedWithTheSecretOfTheSlot() throws Exception {
client().announce(Ctx.background().withTimeout(Duration.ofSeconds(5)), "item.scan.finished",
Map.of("item_id", "0be1d3c6-6f3e-4a6b-a4f5-2a2f2a9f0d10", "score", 75));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expected = "v1=" + Base64.getUrlEncoder().withoutPadding()
.encodeToString(mac.doFinal(seenBody.get().getBytes(StandardCharsets.UTF_8)));
assertThat(seenSignature.get()).isEqualTo(expected);
}
A chave do teste é escrita pelo próprio teste num diretório temporário: nenhuma chave privada literal entra no repositório, e nada é impresso no log do build.
Erros comuns
- Colocar o valor no código ou na configuração — config é lida por quem opera; segredo não. E o valor no repositório é vazamento permanente, mesmo depois de removido.
- Ler
System.getenv("MEU_TOKEN")— variável de ambiente só chega ao pod se estiver declarada; o slot é o caminho do contrato, com validação de permissão e de tamanho. - Passar o segredo para log, mensagem de erro ou
detailsde erro — o SDK redige o que tem forma de segredo, mas o certo é não passar. - Chamar o slot pelo nome do Secret do Kubernetes — o serviço conhece o slot (
notifier-signature), não o nome do Secret (nns-status-mailersend); quem traduz é a plataforma. - Assumir que a chave existe em todo ambiente —
readSecretrecusa quando falta, e é isso que você quer: melhor não subir do que subir sem assinar. - Guardar o valor num campo estático para "não reler" — a rotação do Secret vale para a próxima leitura; reler é barato e é o comportamento esperado.
- Comparar segredo com
equalssem cuidado de tempo — para verificação de valor secreto, use comparação de tempo constante (MessageDigest.isEqual).
Was this page helpful?
Report a problem on this pageDo not send passwords, keys, tokens, or customer data.