0% found this document useful (0 votes)
120 views125 pages

11 Securing Service-To-service APIs - API Security in Action

This document discusses authentication for service-to-service API calls. It describes using API keys or JSON Web Tokens (JWTs) to authenticate at the service level instead of authenticating individual users. The document also discusses using OAuth2 client credentials grants to issue access tokens to services, allowing them to leverage existing OAuth2 implementations. Proper authentication of services is important to secure access and apply rate limits defined in service agreements.

Uploaded by

Marcus Passos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
120 views125 pages

11 Securing Service-To-service APIs - API Security in Action

This document discusses authentication for service-to-service API calls. It describes using API keys or JSON Web Tokens (JWTs) to authenticate at the service level instead of authenticating individual users. The document also discusses using OAuth2 client credentials grants to issue access tokens to services, allowing them to leverage existing OAuth2 implementations. Proper authentication of services is important to secure access and apply rate limits defined in service agreements.

Uploaded by

Marcus Passos
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 125

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

11 Protegendo APIs serviço a serviço


Este capítulocapas

Autenticação de serviços com chaves de API e JWTs


Usando OAuth2 para autorizar chamadas de API de serviço a serviço
Autenticação de certificado de cliente TLS e TLS mútuo
Gerenciamento de credenciais e chaves para serviços
Fazer chamadas de serviço em resposta a solicitações do usuário

Nos capítulos anteriores, a autenticação foi usada para determinar qual usuário
está acessando uma API e o que ele pode fazer. É cada vez mais comum que os
serviços conversem com outros serviços sem que o usuário esteja envolvido. Essas
chamadas de API serviço a serviço podem ocorrer em uma única organização,
como entre microsserviços ou entre organizações quando uma API é exposta para
permitir que outras empresas acessem dados ou serviços. Por exemplo, um vare-
jista on-line pode fornecer uma API para que os revendedores pesquisem produ-
tos e façam pedidos em nome dos clientes. Em ambos os casos, é o cliente da API
que precisa ser autenticado, e não um usuário final. Às vezes, isso é necessário
para cobrança ou para aplicar limites de acordo com um contrato de serviço, mas
também é essencial para a segurança quando dados ou operações confidenciais
podem ser executados. Os serviços geralmente recebem acesso mais amplo do que
usuários individuais, portanto, proteções mais fortes podem ser necessárias por-
que o dano causado pelo comprometimento de uma conta de serviço pode ser
maior do que qualquer conta de usuário individual. Neste capítulo, você apren-
derá como autenticar serviços e reforços adicionais que podem ser aplicados para
melhor proteger contas privilegiadas, usando recursos avançados do OAuth2.

OBSERVAÇÃO Os exemplos neste capítulo requerem uma instalação do Kuber-


netes em execução configurada de acordo com as instruções no apêndice B.

11.1 Chaves de API e autenticação de portador JWT

UmUma das formas mais comuns de autenticação de serviço é uma chave de API,
que é um token de portador simples que identifica o cliente do serviço. Uma
chave de API é muito semelhante aos tokens que você usou para autenticação do
usuário nos capítulos anteriores, exceto que uma chave de API identifica um ser-
viço ou negócio em vez de um usuário e geralmente tem um longo tempo de expi-
ração. Normalmente, um usuário faz login em um site (conhecido como portal do
desenvolvedor) e gera uma chave de API que pode ser adicionada ao ambiente de
produção para autenticar chamadas de API, conforme mostrado na figura 11.1.
Figura 11.1 Para obter acesso a uma API, um representante da organização faz lo-
gin em um portal do desenvolvedor e solicita uma chave de API. O portal gera a
chave de API e a retorna. O desenvolvedor inclui a chave de API como um parâ-
metro de consulta nas solicitações para a API.

A Seção 11.5 aborda as técnicas para implantação segura de chaves de API e ou-
tras credenciais. A chave de API é adicionada a cada solicitação como um parâme-
tro de solicitação ou cabeçalho personalizado.

DEFINIÇÃO Uma chave de API é um token que identifica um cliente de serviço


em vez de um usuário. As chaves de API normalmente são válidas por muito mais
tempo do que um token de usuário, geralmente meses ou anos.

Qualquer um dos formatos de token discutidos nos capítulos 5 e 6 são adequados


para gerar chaves de API, com o nome de usuário substituído por um identifica-
dor para o serviço ou negócio ao qual o uso da API deve estar associado e o tempo
de expiração definido para alguns meses ou anos no futuro. Permissões ou esco-
pos podem ser usados ​para restringir quais chamadas de API podem ser chama-
das por quais clientes e os recursos que eles podem ler ou modificar, assim como
você fez para os usuários nos capítulos anteriores - as mesmas técnicas se
aplicam.

Uma escolha cada vez mais comum é substituir os formatos de chave de API ad
hoc por JSON Web Tokens padrão. Nesse caso, o JWT é gerado pelo portal do de-
senvolvedor com declarações descrevendo o cliente e o tempo de expiração e, em
seguida, assinado ou criptografado com um dos esquemas de criptografia autenti-
cada simétrica descritos no capítulo 6. Isso é conhecido como autenticação de por-
tador JWT, porque o O JWT está agindo como um token de portador puro: qual-
quer cliente que possua o JWT pode usá-lo para acessar as APIs para as quais é vá-
lido sem apresentar nenhuma outra credencial. O JWT geralmente é passado para
a API no cabeçalho Authorization usando o esquema Bearer padrão descrito no
capítulo 5.

DEFINIÇÃO Na autenticação de portador JWT, um cliente obtém acesso a uma


API apresentando um JWT que foi assinado por um emissor no qual a API confia.

Uma vantagem dos JWTs sobre tokens de banco de dados simples ou cadeias de
caracteres criptografadas é que você pode usar assinaturas de chave pública para
permitir que um único portal de desenvolvedor gere tokens aceitos por muitas
APIs diferentes. Somente o portal do desenvolvedor precisa ter acesso à chave pri-
vada usada para assinar os JWTs, enquanto cada servidor de API precisa apenas
acessar a chave pública. O uso de JWTs assinados por chave pública dessa ma-
neira é abordado na seção 7.4.4, e a mesma abordagem pode ser usada aqui, com
um portal de desenvolvedor ocupando o lugar doCOMO.

WARNING Embora o uso de JWTs para autenticação do cliente seja mais seguro
do que os segredos do cliente, um JWT assinado ainda é uma credencial do porta-
dor que pode ser usada por qualquer pessoa que o capture até que expire. Um
servidor de API mal-intencionado ou comprometido pode pegar o JWT e repro-
duzi-lo em outras APIs para representar o cliente. Use expiração, público e outras
declarações JWT padrão (capítulo 6) para reduzir o impacto se um JWT for
comprometido.

11.2 A concessão de credenciais de cliente OAuth2

EmboraA autenticação de portador JWT é atraente devido à sua aparente simpli-


cidade, você ainda precisa desenvolver o portal para gerar JWTs e precisará con-
siderar como revogar tokens quando um serviço for desativado ou uma parceria
comercial for encerrada. A necessidade de lidar com clientes de API serviço a ser-
viço foi antecipada pelos autores das especificações OAuth2, e um tipo de conces-
são dedicado foi adicionado para dar suporte a esse caso: a concessão de creden-
ciais do cliente. Esse tipo de concessão permite que um cliente OAuth2 obtenha
um token de acesso usando suas próprias credenciais sem o envolvimento de um
usuário. O token de acesso emitido pelo servidor de autorização (AS) pode ser
usado como qualquer outro token de acesso, permitindo que uma implantação
OAuth2 existente seja reutilizada para chamadas de API de serviço a serviço. Isso
permite que o AS seja usado como o portal do desenvolvedor e que todos os recur-
sos do OAuth2, como revogação de token detectável e endpoints de introspecção
discutidos no capítulo 7, sejam usados ​para chamadas de serviço.

AVISO Se uma API aceitar chamadas de usuários finais e clientes de serviço, é


importante garantir que a API saiba qual é qual. Caso contrário, os usuários pode-
rão representar clientes de serviço ou vice-versa. Os padrões OAuth2 não definem
uma única maneira de distinguir esses dois casos, portanto, você deve consultar a
documentação do seu fornecedor de AS.

Para obter um token de acesso usando a concessão de credenciais do cliente, o cli-


ente faz uma solicitação HTTPS direta ao token endpoint do AS, especificando a
client_credentials concessãotipo e os escopos que ele requer. O cliente se au-
tentica usando suas próprias credenciais. OAuth2 oferece suporte a vários meca-
nismos de autenticação de clientes diferentes, e você aprenderá sobre vários deles
neste capítulo. O método de autenticação mais simples é conhecido
como client_secret_basic , em que o cliente apresenta seu ID de cliente e um
valor secreto usando a autenticação HTTP Basic. 1 Por exemplo, o comando curl a
seguir mostra como usar a concessão de credenciais do cliente para obter um to-
ken de acesso para um cliente com o ID test e o valor secreto password :

$ curl -u test:password \ ❶
-d 'grant_type=client_credentials&scope=a+b+c' \ ❷
https://as.example.com/access_token

❶ Envie o ID do cliente e o segredo usando a autenticação básica.

❷ Especifique a concessão client_credentials.

Supondo que as credenciais estejam corretas e o cliente esteja autorizado a obter


tokens de acesso usando esta concessão e os escopos solicitados, a resposta será a
seguinte:
{
"access_token": "q4TNVUHUe9A9MilKIxZOCIs6fI0",
"escopo": "ab c",
"token_type": "Portador",
"expire_in": 3599
}

OBSERVAÇÃO Os segredos do cliente OAuth2 não são senhas destinadas a se-


rem lembradas pelos usuários. Eles geralmente são longas strings aleatórias de
alta entropia que são geradas automaticamente durante o registro do cliente.

O token de acesso pode então ser usado para acessar APIs como qualquer outro
token de acesso OAuth2 discutido no capítulo 7. A API valida o token de acesso da
mesma forma que validaria qualquer outro token de acesso, chamando um end-
point de introspecção de token ou diretamente validar o token se for um JWT ou
outro formato independente.

DICA A especificação OAuth2 aconselha as implementações AS a não emitir um


token de atualização ao usar a concessão de credenciais do cliente. Isso ocorre
porque não faz sentido o cliente usar um token de atualização quando ele pode
obter um novo token de acesso usando a concessão de credenciais do cliente
novamente.
11.2.1 Contas de serviço

Comodiscutido no capítulo 8, as contas de usuário geralmente são mantidas em


um diretório LDAP ou outro banco de dados central, permitindo que as APIs pro-
curem usuários e determinem suas funções e permissões. Geralmente, esse não é
o caso dos clientes OAuth2, que geralmente são armazenados em um banco de da-
dos específico do AS, como na Figura 11.2. Uma consequência disso é que a API
pode validar o token de acesso, mas não tem mais informações sobre quem é o cli-
ente para tomar decisões de controle de acesso.
Figura 11.2 Um servidor de autorização (AS) normalmente armazena detalhes do
cliente em um banco de dados privado, portanto, esses detalhes não são acessíveis
para APIs. Uma conta de serviço reside no repositório de usuário compartilhado,
permitindo que as APIs procurem detalhes de identidade, como função ou associ-
ação de grupo.

Uma solução para esse problema é a API tomar decisões de controle de acesso pu-
ramente com base no escopo ou em outras informações relacionadas ao próprio
token de acesso. Nesse caso, os tokens de acesso agem mais como os tokens de ca-
pacidade discutidos no capítulo 9, em que o token concede acesso aos recursos
por conta própria e a identidade do cliente é ignorada. Escopos refinados podem
ser usados ​para limitar a quantidade de acesso concedido.

Como alternativa, o cliente pode evitar a concessão de credenciais do cliente e, em


vez disso, obter um token de acesso para uma conta de serviço. Uma conta de ser-
viço age como uma conta de usuário comum e é criada em um diretório central e
recebe permissões e funções como qualquer outra conta. Isso permite que as APIs
tratem um token de acesso emitido para uma conta de serviço da mesma forma
que um token de acesso emitido para qualquer outro usuário, simplificando o
controle de acesso. Ele também permite que os administradores usem as mesmas
ferramentas para gerenciar contas de serviço que usam para gerenciar contas de
usuário. Ao contrário de uma conta de usuário, a senha ou outras credenciais de
uma conta de serviço devem ser geradas aleatoriamente e de alta entropia, pois
não precisam ser lembradas por um humano.
DEFINIÇÃO Uma conta de serviço é uma conta que identifica um serviço em
vez de um usuário real. As contas de serviço podem simplificar o controle de
acesso e o gerenciamento de contas porque podem ser gerenciadas com as mes-
mas ferramentas que você usa para gerenciar usuários.

Em um fluxo OAuth2 normal, como a concessão de código de autorização, o nave-


gador da Web do usuário é redirecionado para uma página no AS para fazer login
e consentir com a solicitação de autorização. Para uma conta de serviço, o cliente
usa um tipo de concessão não interativo que permite enviar as credenciais da
conta de serviço diretamente para o endpoint do token. O cliente deve ter acesso
às credenciais da conta de serviço, portanto, geralmente há uma conta de serviço
dedicada a cada cliente. O tipo de concessão mais simples de usar é o tipo de con-
cessão ROPC (Resource Owner Password Credentials), no qual o nome de usuário
e a senha da conta de serviço são enviados para o endpoint do token como cam-
pos de formulário:

$ curl -u teste:senha \ ❶
-d 'grant_type=password&scope=a+b+c' \
-d 'username=serviceA&password=password' \ ❷
https://as.example.com/access_token

❶ Envie o ID do cliente e o segredo usando autenticação básica.

❷ Passe a senha da conta do serviço nos dados do formulário.


Isso resultará na emissão de um token de acesso para o test clientecom a conta
de serviço serviceA como o proprietário do recurso.

AVISO Embora o tipo de concessão ROPC seja mais seguro para contas de serviço
do que para usuários finais, há métodos de autenticação melhores disponíveis
para clientes de serviço discutidos nas seções 11.3 e 11.4. O tipo de concessão
ROPC pode ser preterido ou removido em versões futuras do OAuth.

A principal desvantagem das contas de serviço é a exigência de que o cliente ge-


rencie dois conjuntos de credenciais, um como cliente OAuth2 e outro para a
conta de serviço. Isso pode ser eliminado organizando-se para que as mesmas cre-
denciais sejam usadas para ambos. Como alternativa, se o cliente não precisar
usar recursos do AS que exigem credenciais do cliente, ele pode ser um cliente pú-
blico e usar apenas as credenciais da conta de serviçoporAcesso.

questionário

1. Quais das opções a seguir são diferenças entre uma chave de API e um token de
autenticação do usuário?
1. As chaves de API são mais seguras do que os tokens de usuário.
2. As chaves de API só podem ser usadas durante o horário comercial normal.
3. Um token de usuário normalmente é mais privilegiado do que uma chave de
API.
4. Uma chave de API identifica um serviço ou negócio em vez de um usuário.
5. Uma chave de API geralmente tem um tempo de expiração mais longo do que
um token de usuário.
2. Qual dos seguintes tipos de concessão é mais facilmente usado para autenticar
uma conta de serviço?
1. PKCE
2. Hugh Grant
3. Concessão implícita
4. Concessão de código de autorização
5. Concessão de credenciais de senha do proprietário do recurso

As respostas estão no final do capítulo.

11.3 A concessão do portador JWT para OAuth2

OBSERVAÇÃO Para executar os exemplos desta seção, você precisará de um


servidor de autorização OAuth2 em execução. Siga as instruções no apêndice A
para configurar o AS e um cliente de teste antes de continuar com esta seção.

Autenticaçãocom um segredo de cliente ou senha de conta de serviço é muito sim-


ples, mas sofre de várias desvantagens:

Alguns recursos do OAuth2 e OIDC exigem que o AS consiga acessar os bytes


brutos do segredo do cliente, evitando o uso de hash. Isso aumenta o risco se o
banco de dados do cliente for comprometido, pois um invasor pode recuperar
todos os segredos do cliente.
Se as comunicações com o AS forem comprometidas, um invasor pode roubar
os segredos do cliente à medida que são transmitidos. Na seção 11.4.6, você
verá como proteger os tokens de acesso contra essa possibilidade, mas os segre-
dos do cliente são inerentemente vulneráveis ​a serem roubados.
Pode ser difícil alterar o segredo de um cliente ou a senha de uma conta de ser-
viço, especialmente se ela for compartilhada por muitos servidores.

Por esses motivos, é benéfico usar um mecanismo de autenticação alternativo.


Uma alternativa suportada por muitos servidores de autorização é o tipo de con-
cessão JWT Bearer para OAuth2, definido na RFC 7523 (
https://tools.ietf.org/html/rfc7523). Essa especificação permite que um cliente obte-
nha um token de acesso apresentando um JWT assinado por uma parte confiável,
seja para se autenticar para a concessão de credenciais do cliente ou para trocar
um JWT que representa a autorização de um usuário ou conta de serviço. No pri-
meiro caso, o JWT é assinado pelo próprio cliente por meio de uma chave que ele
controla. No segundo caso, o JWT é assinado por alguma autoridade confiável do
AS, como um provedor OIDC externo. Isso pode ser útil se o AS quiser delegar au-
tenticação e consentimento do usuário a um serviço terceirizado. Para autentica-
ção de conta de serviço, o cliente geralmente é diretamente confiável com as cha-
ves para assinar JWTs em nome dessa conta de serviço porque há uma conta de
serviço dedicada para cada cliente. Na seção 11.5.3,

Ao usar um algoritmo de assinatura de chave pública, o cliente precisa fornecer


apenas a chave pública ao AS, reduzindo o risco se o AS for comprometido porque
a chave pública só pode ser usada para verificar assinaturas e não criá-las. Adicio-
nar um tempo de expiração curto também reduz os riscos ao autenticar em um
canal inseguro, e alguns servidores suportam a lembrança de IDs JWT usados ​an-
teriormente para evitar a repetição.

Outra vantagem da autenticação do portador JWT é que muitos servidores de au-


torização suportam a busca das chaves públicas do cliente no formato JWK de um
terminal HTTPS. O AS buscará periodicamente as chaves mais recentes do termi-
nal, permitindo que o cliente altere suas chaves regularmente. Isso efetivamente
inicializa a confiança nas chaves públicas do cliente usando o web PKI: o AS con-
fia nas chaves porque elas foram carregadas de um URI que o cliente especificou
durante o registro e a conexão foi autenticada usando TLS, impedindo que um in-
vasor injete chaves falsas. oO formato JWK Set permite que o cliente forneça mais
de uma chave, permitindo que ele continue usando a antiga chave de assinatura
até ter certeza de que o AS pegou a nova (figura 11.3).
Figura 11.3 O cliente publica sua chave pública em um URI que ele controla e re-
gistra esse URI no AS. Quando o cliente autentica, o AS recupera sua chave pública
por HTTPS do URI registrado. O cliente pode publicar uma nova chave pública
sempre que quiser alterar a chave.

11.3.1 Autenticação do cliente

Paraobter um token de acesso sob sua própria autoridade, um cliente pode usar
autenticação de cliente de portador JWT com a concessão de credenciais do cli-
ente. O cliente executa a mesma solicitação que você fez na seção 11.2, mas em
vez de fornecer um segredo do cliente usando a autenticação básica, você fornece
um JWT assinado com a chave privada do cliente. Quando usado para autentica-
ção, o JWT também é conhecido como uma asserção do cliente.

DEFINIÇÃO Uma afirmaçãoé um conjunto assinado de declarações de identi-


dade usadas para autenticação ou autorização.

Para gerar o par de chaves pública e privada a ser usado para assinar o JWT, você
pode usar keytool na linha de comando, como segue. Keytool irá gerar um certi-
ficado para TLS ao gerar um par de chaves públicas, então use a -dname op-
çãopara especificar o nome do assunto. Isso é necessário mesmo que você não use
o certificado. Você será solicitado a fornecer a senha do armazenamento de
chaves.

keytool -genkeypair \
-keystore keystore.p12 \ ❶
-keyalg EC -keysize 256 -alias es256-key \ ❷
-dname cn=test ❸

❶ Especifique o keystore.

❷ Use o algoritmo EC e tamanho de chave de 256 bits.

❸ Especifique um nome distinto para o certificado.


DICA Keytool escolhe uma curva elíptica apropriada com base no tamanho da
chave e, neste caso, escolhe a curva P-256 correta necessária para o ES256 algo-
ritmo. Existem outras curvas elípticas de 256 bits que são incompatíveis. No Java
12 e posterior, você pode usar o -groupname secp256r1 argumentopara especi-
ficar explicitamente a curva correta. Para ES384 o nome do grupo é
secp384r1 e para ES512 ele é secp521r1 (nota: 521 não 512). Keytool não pode
gerar chaves EdDSA no momento.

Você pode então carregar a chave privada do keystore da mesma forma que fez
nos capítulos 5 e 6 para as chaves HMAC e AES. A biblioteca JWT requer que a
chave seja convertida para o ECPrivateKey tipo específico, faça isso ao carregá-
lo. A Listagem 11.1 mostra o início de uma JwtBearerClient classeque você es-
creverá para implementar a autenticação do portador JWT. Navegue até
src/main/java/com/manning/apisecurityinaction e crie um novo arquivo chamado
JwtBearerClient.java. Digite o conteúdo da listagem e salve o arquivo. Ainda não
faz muito, mas você o expandirá a seguir. A listagem contém todas as instruções
de importação necessárias para concluir o curso.

Listagem 11.1 Carregando a chave privada

pacote com.manning.apisecurityinaction;

importar java.io.FileInputStream;
importar java.net.URI;
importar java.net.http.*;
importar java.security.KeyStore;
importar java.security.interfaces.ECPrivateKey;
importar java.util.*;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jwt.*;

importar java.time.Instant.now estático;


importar estático java.time.temporal.ChronoUnit.SECONDS;
importar spark.Spark.* estático;

public class JwtBearerClient {


public static void main(String... args) lança Exception {
var senha = "alterar".toCharArray();
var keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("keystore.p12"),
senha);
var privateKey = (ECPrivateKey) ❶
keyStore.getKey("es256-key", senha); ❶
}
}

❶ Converta a chave privada para o tipo necessário.


Para que o AS consiga validar o JWT assinado que você envia, ele precisa saber
onde encontrar a chave pública do seu cliente. Conforme discutido na introdução
da seção 11.3, uma maneira flexível de fazer isso é publicar sua chave pública
como um Conjunto JWK, pois isso permite que você altere sua chave regular-
mente simplesmente publicando uma nova chave no Conjunto JWK. A biblioteca
Nimbus JOSE+JWT que você usou no capítulo 5 suporta a geração de um JWK Set
a partir de um keystore usando o JWKSet.load método, conforme a Listagem
11.2. Depois de carregar o JWK Set, use o toPublicJWKSet métodopara garantir
que ele contenha apenas os detalhes da chave pública e não as chaves privadas.
Você pode então usar o Spark para publicar o conjunto JWK em um URI HTTPS
usando o application/jwk-set+json tipo de conteúdo padrão. Certifique-se
de ativar o suporte TLS usando o secure métodopara que as chaves não possam
ser adulteradas em trânsito, conforme discutido no capítulo 3. Abra o arquivo
JwtBearerClient.java novamente e adicione o código da listagem ao método prin-
cipal, após o código existente.

AVISO Certifique-se de não esquecer a .toPublicJWKSet() chamada do mé-


todo. Caso contrário, você publicará suas chaves privadas na Internet!

Listagem 11.2 Publicando um Conjunto JWK

var jwkSet = JWKSet.load(keyStore, alias -> senha) ❶


.toPublicJWKSet(); ❷
secure("localhost.p12", "changeit", nulo, nulo); ❸
get("/jwks", (solicitação, resposta) -> { ❸
response.type("application/jwk-set+json"); ❸
return jwkSet.toString(); ❸
}); ❸

❶ Carregue o conjunto JWK do armazenamento de chaves.

❷ Certifique-se de que contém apenas chaves públicas.

❸ Publique o Conjunto JWK em um endpoint HTTPS usando o Spark.

A biblioteca Nimbus JOSE requer que a biblioteca criptográfica Bouncy Castle seja
carregada para habilitar o suporte JWK Set, então adicione a seguinte dependên-
cia ao arquivo Maven pom.xml na raiz do projeto Natter API:

<dependência>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.66</version>
</dependência>

Agora você pode iniciar o cliente executando o seguinte comando na pasta raiz do
projeto Natter API:
mvn clean compile exec:java \
-Dexec.mainClass=com.manning.apisecurityinaction.JwtBearerClient

Em um terminal separado, você pode testar se as chaves públicas estão sendo pu-
blicadas executando:

curl https://localhost:4567/jwks > jwks.txt

O resultado será um objeto JSON contendo um único keys campo, que é uma ma-
triz de JSON Web Keys.

Por padrão, o servidor AS em execução no Docker não poderá acessar o URI no


qual você publicou as chaves, portanto, para este exemplo, você pode copiar o
JWK Set diretamente nas configurações do cliente. Se você estiver usando oSoft-
ware ForgeRock Access Management do apêndice A, faça login no console de ad-
ministração como amadmin conforme descrito no apêndice e execute o
seguintedegraus:

1. Navegue até Top Level Realm e clique em Applications no menu à esquerda e


depois em OAuth2.0.
2. Clique no cliente de teste que você cadastrou ao instalar o AS.
3. Selecione a guia Signing and Encryption e copie e cole o conteúdo do arquivo
jwks.txt que você acabou de salvar no campo Json Web Key.
4. Encontre o campo Token Endpoint Authentication Signing Algorithm logo
acima do campo JWK e altere-o para ES256.
5. Altere o campo Seletor de chave pública para “JWKs” para garantir que as cha-
ves que você acabou de configurar sejam usadas.
6. Por fim, role para baixo e clique em Salvar alterações no canto inferior direito
da tela.

11.3.2 Gerando o JWT

UMAO JWT usado para autenticação do cliente deve conter as seguintes


declarações:

A sub reclamaçãoé o ID do cliente.


uma iss reivindicaçãoque indica quem assinou o JWT. Para autenticação do
cliente, geralmente também é o ID do cliente.
uma aud reivindicaçãoque lista o URI do endpoint de token do AS como o pú-
blico-alvo.
uma exp reivindicaçãoque limita o tempo de expiração do JWT. Um AS pode
rejeitar um JWT de autenticação de cliente com um tempo de expiração excessi-
vamente longo para reduzir o risco de ataques de repetição.

Alguns servidores de autorização também exigem que o JWT contenha uma


jti declaração com um valor aleatório exclusivo. O AS pode lembrar o jti valo-
raté que o JWT expire para evitar a repetição se o JWT for interceptado. Isso é
muito improvável porque a autenticação do cliente ocorre em uma conexão TLS
direta entre o cliente e o AS, mas o uso de um jti é exigido pelas especificações
do OpenID Connect, portanto, você deve adicionar um para garantir a compatibi-
lidade máxima. A Listagem 11.3 mostra como gerar um JWT no formato correto
usando a biblioteca Nimbus JOSE+JWT que você usou no capítulo 6. Nesse caso,
você usará o ES256 algoritmo de assinatura (ECDSA com SHA-256), amplamente
implementado. Gere um cabeçalho JWT indicando o algoritmo e o ID da chave
(que corresponde ao alias do keystore). Preencha os valores do conjunto de decla-
rações JWT conforme discutido. Por fim, assine o JWT para produzir o valor da as-
serção. Abra o arquivo JwtBearerClient.java e digite o conteúdo da listagem no fi-
nal do main método.

Listagem 11.3 Gerando uma asserção de cliente JWT

var clientId = "teste";


var as = "https://as.example.com:8080/oauth2/access_token";
var header = new JWSHeader.Builder(JWSAlgorithm.ES256) ❶
.keyID("es256-key") ❶
.build(); ❶
var reivindicações = new JWTClaimsSet.Builder()
.subject(clientId) ❷
.issuer(clientId) ❷
.expirationTime(Date.from(now().plus(30, SECONDS))) ❸
.audience(as) ❹
.jwtID(UUID.randomUUID().toString() ) ❺
.construir();
var jwt = new SignedJWT(cabeçalho, declarações); ❻
jwt.sign(new ECDSASigner(privateKey)); ❻
var assertion = jwt.serialize(); ❻

❶ Crie um cabeçalho com o algoritmo e ID de chave corretos.

❷ Defina as declarações do assunto e do emissor para o ID do cliente.

❸ Adicione um tempo de expiração curto.

❹ Defina o público para o endpoint do token AS.

❺ Adicione uma declaração de ID JWT aleatória para evitar a repetição.

❻ Assine o JWT com a chave privada.

Depois de registrar o conjunto JWK com o AS, você poderá gerar uma declaração e
usá-la para autenticar o AS para obter um token de acesso. A Listagem 11.4 mos-
tra como formatar a solicitação de credenciais do cliente com a asserção do cli-
ente e enviá-la ao AS como uma solicitação HTTP. A asserção JWT é passada como
um novo client_assertion parâmetro, e o client_assertion_type parâme-
troé usado para indicar que a declaração é um JWT especificando o valor:

urn:ietf:params:oauth:client-assertion-type:jwt-bearer
Os parâmetros de formulário codificados são, então, POSTados para o endpoint de
token AS usando a biblioteca Java HTTP. Abra o arquivo JwtBearerClient.java no-
vamente e adicione o conteúdo da listagem ao final do main método.

Listagem 11.4 Enviando a solicitação ao AS

var form = "grant_type=client_credentials&scope=create_space" + ❶


"&client_assertion_type=" + ❶
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + ❶
"&client_assertion=" + assertion; ❶
var httpClient = HttpClient.newHttpClient(); ❷
var request = HttpRequest.newBuilder() ❷
.uri(URI.create(as)) ❷
.header("Content-Type", "application/x-www-form-urlencoded") ❷
.POST(HttpRequest.BodyPublishers.ofString(form)) ❷
.build(); ❷
var response = httpClient.send(request, ❸
HttpResponse.BodyHandlers.ofString()); ❸
System.out.println(response.statusCode());
System.out.println(response.body());

❶ Crie o conteúdo do formulário com o assertion JWT.

❷ Crie a solicitação POST para o endpoint do token.

❸ Envie a solicitação e analise a resposta.


Execute o seguinte comando Maven para testar o cliente e receber um token de
acesso do AS:

mvn -q compilação limpa exec:java \


-Dexec.mainClass=com.manning.apisecurityinaction.JwtBearerClient

Depois que o fluxo do cliente for concluído, ele imprimirá a resposta do token de
acesso doCOMO.

11.3.3 Autenticação da conta de serviço

autenticandouma conta de serviço usando autenticação de portador JWT funci-


ona muito como autenticação de cliente. Em vez de usar a concessão de credenci-
ais do cliente, um novo tipo de concessão chamado

urn:ietf:params:oauth:grant-type:jwt-bearer

é usado e o JWT é enviado como o valor do assertion parâmetroem vez do


client_assertion parâmetro. O trecho de código a seguir mostra como cons-
truir o formulário ao usar o tipo de concessão de portador JWT para autenticar
usando uma conta de serviço:

var form = "grant_type=" + ❶


"urn:ietf:params:oauth:grant-type:jwt-bearer" + ❶
"&scope=create_space&assertion=" + assertion; ❷

❶ Use o tipo de concessão jwt-bearer.

❷ Passe o JWT como parâmetro de asserção.

As declarações no JWT são as mesmas usadas para autenticação do cliente, com as


seguintes exceções:

A sub reclamaçãodeve ser o nome de usuário da conta de serviço em vez do ID


do cliente.
A iss reclamaçãotambém pode ser diferente do ID do cliente, dependendo de
como o AS está configurado.

Há uma diferença importante nas propriedades de segurança dos dois métodos, e


isso geralmente se reflete em como o AS é configurado. Quando o cliente está
usando um JWT para se autenticar, o JWT é uma autoafirmação de identidade. Se
a autenticação for bem-sucedida, o AS emite um token de acesso autorizado pelo
próprio cliente. Na concessão do portador JWT, o cliente afirma que está autori-
zado a receber um token de acesso em nome do usuário fornecido, que pode ser
uma conta de serviço ou um usuário real. Como o usuário não está presente para
consentir com essa autorização, o AS geralmente aplicará verificações de segu-
rança mais fortes antes de emitir o token de acesso. Caso contrário, um cliente po-
deria solicitar tokens de acesso para qualquer usuário de sua preferência sem que
o usuário estivesse envolvido. Por exemplo,
Um aspecto interessante da autenticação de portador JWT é que o emissor do JWT
e o cliente podem ser partes diferentes. Você usará esse recurso na seção 11.5.3
para fortalecer a segurança de um ambiente de serviço, garantindo que os pods
em execução no Kubernetes não tenham acesso direto a privilégiosserviçocreden-
ciais.

questionário

3. Qual das opções a seguir é o principal motivo para preferir uma conta de ser-
viço em vez da concessão de credenciais do cliente?
1. As credenciais do cliente têm maior probabilidade de serem comprometidas.
2. É difícil limitar o escopo de uma solicitação de concessão de credenciais de
cliente.
3. É mais difícil revogar as credenciais do cliente se a conta estiver
comprometida.
4. A concessão de credenciais do cliente usa uma autenticação mais fraca do
que as contas de serviço.
5. Os clientes geralmente são privados do AS, enquanto as contas de serviço po-
dem residir em um repositório compartilhado.
4. Quais dos seguintes são motivos para preferir a autenticação do portador JWT à
autenticação secreta do cliente? (Pode haver várias respostas corretas.)
1. Os JWTs são mais simples que os segredos do cliente.
2. Os JWTs podem ser compactados e, portanto, são menores que os segredos
do cliente.
3. O AS pode precisar armazenar o segredo do cliente em um formato
recuperável.
4. Um JWT pode ter um tempo de expiração limitado, reduzindo o risco de
roubo.
5. A autenticação do portador JWT evita o envio de um segredo de longa dura-
ção pela rede.

As respostas estão no final do capítulo.

11.4 Autenticação TLS mútua

JWTa autenticação do portador é mais segura do que enviar um segredo do cli-


ente para o AS, mas como você viu na seção 11.3.1, pode ser significativamente
mais complicado para o cliente. OAuth2 requer que as conexões com o AS sejam
feitas usando TLS, e você também pode usar TLS para autenticação segura do cli-
ente. Em uma conexão TLS normal, apenas o servidor apresenta um certificado
que autentica quem é. Conforme explicado no capítulo 10, isso é tudo o que é ne-
cessário para configurar um canal seguro conforme o cliente se conecta ao servi-
dor, e o cliente precisa ter certeza de que se conectou ao servidor certo e não a
uma falsificação maliciosa. Mas o TLS também permite que o cliente se autenti-
que opcionalmente com um certificado de cliente, permitindo que o servidor te-
nha certeza da identidade do cliente e use isso para decisões de controle de
acesso. Você pode usar esse recurso para fornecer autenticação segura de clientes
de serviço. Quando ambos os lados da conexão são autenticados, isso é conhecido
como TLS mútuo (mTLS).
DICA Embora antes se esperasse que a autenticação de certificado de cliente
fosse usada para usuários, talvez até mesmo substituindo senhas, ela é muito ra-
ramente usada. A complexidade do gerenciamento de chaves e certificados torna
a experiência do usuário muito ruim e confusa. Métodos modernos de autentica-
ção de usuário, como WebAuthn( https://webauthn.guide ) fornecem muitos dos
mesmos benefícios de segurança e são muito mais fáceis de usar.

11.4.1 Como funciona a autenticação do certificado TLS

oDetalhes completos de como funciona a autenticação de certificado TLS levariam


muitos capítulos por conta própria, mas um esboço de como o processo funciona
no caso mais comum ajudará você a entender as propriedades de segurança for-
necidas. A comunicação TLS é dividida em duas fases:

1. Um aperto de mão inicial, em que o cliente e o servidor negociam quais algorit-


mos criptográficos e extensões de protocolo usar, opcionalmente autenticam
um ao outro e concordam em chaves de sessão compartilhadas.
2. Dados de um aplicativofase de transmissão na qual o cliente e o servidor usam
as chaves de sessão compartilhadas negociadas durante o handshake para tro-
car dados usando criptografia autenticada simétrica. 2

Durante o handshake, o servidor apresenta seu próprio certificado em uma men-


sagem de certificado TLS. Normalmente, este não é um único certificado, mas
uma cadeia de certificados, conforme descrito no capítulo 10: o certificado do ser-
vidor é assinado por uma autoridade de certificação (CA) e o certificado da CA
também está incluído. A CA pode ser uma CA intermediária, caso em que outra CA
também assina seu certificado e assim por diante até que no final da cadeia esteja
uma CA raiz na qual o cliente confia diretamente. O certificado CA raiz geral-
mente não é enviado como parte da cadeia, pois o cliente já possui uma cópia.

RECAPITULAR um certificadocontém uma chave pública e informações de


identidade do assunto para o qual o certificado foi emitido e é assinado por uma
autoridade de certificação. Uma cadeia de certificadosconsiste no certificado do
servidor ou cliente seguido pelos certificados de uma ou mais CAs. Cada certifi-
cado é assinado pela CA que o segue na cadeia até que uma CA raizé alcançado
que é diretamente confiável pelo destinatário.

Para ativar a autenticação de certificado de cliente, o servidor envia uma mensa-


gem CertificateRequest, que solicita que o cliente também apresente um certifi-
cado e, opcionalmente, indica quais CAs ele está disposto a aceitar certificados as-
sinados e os algoritmos de assinatura que ele suporta. Se o servidor não enviar
esta mensagem, a autenticação do certificado do cliente será desativada. O cliente
então responde com sua própria mensagem de certificado contendo sua cadeia de
certificados. O cliente também pode ignorar a solicitação de certificado e o servi-
dor pode escolher se aceita ou não a conexão.

OBSERVAÇÃO A descrição nesta seção é do handshake TLS 1.3 (simplificado).


Versões anteriores do protocolo usam mensagens diferentes, mas o processo é
equivalente.
Se isso fosse tudo o que estava envolvido na autenticação do certificado TLS, não
seria diferente da autenticação do portador JWT, e o servidor poderia pegar os
certificados do cliente e apresentá-los a outros servidores para representar o cli-
ente ou vice-versa. Para evitar isso, sempre que o cliente ou servidor apresentar
uma mensagem de certificado, o TLS exige que eles também enviem uma mensa-
gem CertificateVerifyem que assinam uma transcrição de todas as mensagens an-
teriores trocadas durante o aperto de mão. Isso prova que o cliente (ou servidor)
tem o controle da chave privada correspondente ao seu certificado e garante que
a assinatura esteja fortemente vinculada a esse handshake específico: há valores
únicos trocados no handshake, evitando que a assinatura seja reutilizada para
qualquer outro TLS sessão. As chaves de sessão usadas para criptografia autenti-
cada após o handshake também são derivadas desses valores exclusivos, garan-
tindo que essa assinatura durante o handshake efetivamente autentique toda a
sessão, independentemente da quantidade de dados trocados. A Figura 11.4 mos-
tra as principais mensagens trocadas no TLS 1.3aperto de mão.
Figura 11.4 No handshake TLS, o servidor envia seu próprio certificado e pode so-
licitar um certificado ao cliente usando uma mensagem CertificateRequest. O cli-
ente responde com uma mensagem Certificate contendo o certificado e uma men-
sagem CertificateVerify provando que possui a chave privada associada.

SAIBA MAIS Fornecemos apenas um breve esboço do processo de handshake


TLS e autenticação de certificado. Um excelente recurso para aprender mais é
Bulletproof SSL e TLS de Ivan Ristic' (Feisty Duck, 2015).

questionário

5. Para solicitar autenticação de certificado de cliente, o servidor deve enviar qual


das seguintes mensagens?
1. Certificado
2. ClienteOlá
3. ServidorOlá
4. CertificateVerify
5. Solicitação de certificado
6. Como o TLS impede que uma mensagem CertificateVerify capturada seja reuti-
lizada para uma sessão TLS diferente? (Escolha uma resposta.)
1. A palavra do cliente é a sua honra.
2. A mensagem CertificateVerify tem um tempo de expiração curto.
3. O CertificateVerify contém uma assinatura sobre todas as mensagens anteri-
ores no handshake.
4. O servidor e o cliente lembram-se de todas as mensagens do CertificateVerify
que já viram.
As respostas estão no final do capítulo.
11.4.2 Autenticação do certificado do cliente

Parahabilite a autenticação de certificado de cliente TLS para clientes de serviço,


você precisa configurar o servidor para enviar uma mensagem CertificateRe-
questcomo parte do aperto de mão e para validar qualquer certificado que re-
ceba. A maioria dos servidores de aplicativos e proxies reversos oferece suporte a
opções de configuração para solicitação e validação de certificados de cliente, mas
elas variam de produto para produto. Nesta seção, você configurará o controlador
de entrada Nginx do capítulo 10 para permitir certificados de cliente e verificar se
eles são assinados por uma CA confiável.

Para habilitar a autenticação de certificado de cliente no controlador de entrada


do Kubernetes, você pode adicionar anotações à definição de recurso de entrada
no projeto Natter. A Tabela 11.1 mostra as anotações que podem ser usadas.

NOTA Todos os valores de anotação devem estar entre aspas duplas, mesmo que
não sejam strings. Por exemplo, você deve usar
nginx.ingress.kubernetes.io/ auth-tls-verify-depth: "1" para espe-
cificar um comprimento máximo de cadeia de 1.
Tabela 11.1 Anotações do controlador de entrada Kubernetes Nginx para autenti-
cação de certificado de cliente

Anotação Valores permitidos Descrição

nginx.ingress.kubernetes.io/auth- on , off , Ativa ou desativa a


tls-verify-client optional ou optional_no_ca autenticação de ce
ficado de cliente. S
on , um certificado
de cliente é necess
rio. O optional v
lor solicita um cert
cado e verifica se o
cliente apresenta u
A
optional_no_ca
ção solicita um cer
ficado ao cliente, m
não o verifica.

nginx.ingress.kubernetes.io/auth- O nome de um segredo do Ku- O segredo contém


tls-secret bernetes no conjunto de CAs co
formato namespace/secret- fiáveis ​para verific
name o certificado do
cliente.
nginx.ingress.kubernetes.io/auth- Um inteiro positivo O número máximo
tls-verify-depth certificados de CA
termediários perm
dos na cadeia de c
tificados do cliente

nginx.ingress.kubernetes.io/auth- true ou false Se ativado, o certif


tls-pass-certificate-to-upstream cado do cliente ser
disponibilizado
no ssl-client-
cert Cabeçalho HT
para servidores po
trás da entrada.

nginx.ingress.kubernetes.io/auth- um URL Se a autenticação d


tls-error-page certificado falhar,
cliente será redire
nado para esta pá-
gina de erro.

Para criar o segredo com os certificados de CA confiáveis ​para verificar quaisquer


certificados de cliente, crie um segredo genérico passando em um arquivo de cer-
tificado codificado por PEM. Você pode incluir vários certificados de CA raiz no
arquivo simplesmente listando-os um após o outro. Para os exemplos neste capí-
tulo, você pode usar certificados de cliente gerados pelo mkcert utilitárioque
você usou desde o capítulo 2. O certificado CA raiz para mkcert está instalado em
seu diretório CAROOT, que você pode determinar executando

mkcert -CAROOT

que produzirá uma saída como a seguinte:

/Users/neil/Library/Application Support/mkcert

Para importar esta CA raiz como um segredo do Kubernetes no formato correto,


execute o seguinte comando:

kubectl criar segredo genérico ca-secret -n natter-api \


--from-file=ca.crt="$(mkcert -CAROOT)/rootCA.pem"

A Listagem 11.5 mostra uma configuração de entrada atualizada com suporte


para autenticação de certificado de cliente opcional. A verificação do cliente é de-
finida como opcional, para que a API possa oferecer suporte a clientes de serviço
usando autenticação de certificado e usuários executando autenticação de senha.
O segredo TLS para os certificados de CA confiáveis ​é definido para natter-
api/ ca-secret corresponder ao segredo que você acabou de criar no natter-
api namespace. Por fim, você pode habilitar a passagem do certificado para hosts
upstream para poder extrair a identidade do cliente do certificado. Navegue até a
pasta kubernetes no projeto Natter API e atualize o arquivo natter-ingress.yaml
para adicionar as novas anotações mostradas em negrito na listagem a seguir.

Listagem 11.5 Entrada com autenticação de certificado de cliente opcional

apiVersão: extensões/v1beta1
tipo: entrada
metadados:
nome: api-ingress
namespace: natter-api
anotações:
nginx.ingress.kubernetes.io/upstream-vhost:
"$service_name.$namespace.svc.cluster.local:$service_port"
nginx.ingress.kubernetes.io/auth-tls-verify-client: "opcional" ❶
nginx.ingress.kubernetes.io/auth-tls-secret: "natter-api/ca-secret" ❶
nginx.ingress.kubernetes. io/auth-tls-verify-depth: "1" ❶
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: ❶
"true" ❶
especificação:
tls:
- anfitriões:
- api.natter.local
secretName: natter-tls
as regras:
- host: api.natter.local
http:
caminhos:
- Processo interno:
serviceName: natter-api-service
servicePort: 4567

❶ Anotações para permitir autenticação de certificado de cliente opcional

Se você ainda tiver o Minikube em execução no capítulo 10, agora pode atualizar
a definição de entradacorrida:

kubectl apply -f kubernetes/natter-ingress.yaml

DICA Se as alterações no controlador de ingresso não estiverem funcionando, ve-


rifique a saída de kubectl describe ingress -n natter-api para garantir
que as anotações estejam corretas. Para mais dicas de solução de problemas, veri-
fique a documentação oficial em http://mng.bz/X0rG .

11.4.3 Verificando a identidade do cliente

oa verificação realizada pelo NGINX limita-se a verificar se o cliente forneceu um


certificado assinado por uma das CAs confiáveis ​e se todas as restrições especifi-
cadas nos próprios certificados foram atendidas, como o tempo de expiração do
certificado. Para verificar a identidade do cliente e aplicar as permissões apropri-
adas, o controlador de entrada define vários cabeçalhos HTTP que você pode usar
para verificar os detalhes do certificado do cliente, mostrados na tabela 11.2.
Tabela 11.2 Cabeçalhos HTTP definidos pelo NGINX

Cabeçalho Descrição

ssl- Indica se um certificado de cliente foi apresentado e, em caso


cli- afirmativo, se foi verificado. Os valores possíveis são NONE para
ent- indicar que nenhum certificado foi fornecido, SUCCESS se um
verify certificado foi apresentado e é válido ou FAILURE:<reason> se
um certificado foi fornecido, mas é inválido ou não foi assinado
por uma CA confiável.

ssl- O Nome Distinto do Assunto(DN) do certificado, caso tenha sido


cli- fornecido.
ent-
sub-
ject-
dn

ssl- O DN do emissor, que corresponderá ao DN do assunto do certifi-


cli- cado CA.
ent-
is-
suer-
dn
ssl- Se auth-tls-pass-certificate-to-upstream estiver ativado,
cli- ele conterá o certificado de cliente completo no formato PEM co-
ent- dificado por URL.
cert

A Figura 11.5 mostra o processo geral. O controlador de entrada NGINX encerra a


conexão TLS do cliente e verifica o certificado do cliente durante o handshake
TLS. Após a autenticação do cliente, o controlador de entrada encaminha a solici-
tação para o serviço de back-end e inclui o certificado de cliente verificado no
ssl-client-cert cabeçalho.
Figura 11.5 Para permitir a autenticação de certificado de cliente por clientes ex-
ternos, configure o controlador de entrada NGINX para solicitar e verificar o certi-
ficado de cliente durante o handshake TLS. O NGINX encaminha o certificado do
cliente no ssl-client-cert cabeçalho HTTP.

o mkcert utilitárioque você usará para desenvolvimento neste capítulo define o


nome do cliente que você especifica como uma extensão Subject Alternative
Name (SAN) no certificado em vez de usar o campo Subject DN. Como o NGINX
não expõe os valores SAN diretamente em um cabeçalho, você precisará analisar
o certificado completo para extraí-lo. A Listagem 11.5 mostra como analisar o ca-
beçalho fornecido pelo NGINX em um java.security.cert
.X509Certificate objetousando um CertificateFactory , do qual você pode
extrair o identificador do cliente do SAN. Abra o arquivo UserController.java e in-
clua o novo método da listagem 11.6. Você também precisará adicionar as seguin-
tes instruções de importação na parte superior do arquivo:

importar java.io.ByteArrayInputStream;
importar java.net.URLDecoder;
importar java.security.cert.*;

Listagem 11.6 Analisando um certificado

public static X509Certificate decodeCert(String encodedCert) {


var pem = URLDecoder.decode(encodedCert, UTF_8); ❶
try (var in = new ByteArrayInputStream(pem.getBytes(UTF_8))) {
var certFactory = CertificateFactory.getInstance("X.509"); ❷
return (X509Certificate) certFactory.generateCertificate(in); ❷
} catch (Exceção e) {
lança nova RuntimeException(e);
}
}

❶ Decodifique a codificação de URL adicionada pelo NGINX.


❷ Analisar o certificado codificado por PEM usando um CertificateFactory.

Pode haver várias entradas SAN em um certificado e cada entrada pode ter um
tipo diferente. Mkcert usa o tipo DNS, então o código procura a primeira entrada
DNS SAN e a retorna como o nome. List Java retorna as entradas SAN como uma
coleção de objetos de dois elementos, o primeiro dos quais é o tipo (como um nú-
mero inteiro) e o segundo é o valor real (um String ou uma matriz de bytes, de-
pendendo do tipo). As entradas DNS têm valor de tipo 2. Se o certificado contiver
uma entrada correspondente, você poderá definir a ID do cliente como o atributo
de assunto na solicitação, exatamente como fez ao autenticar usuários. Como a CA
confiável emite certificados de cliente, você pode instruir a CA a não emitir um
certificado que entre em conflito com o nome de um usuário existente. Abra o ar-
quivo UserController.java novamente e inclua a nova definição de constante e
método da listagem a seguir.

Listagem 11.7 Analisando um certificado de cliente

private static final int DNS_TYPE = 2;


void processClientCertificateAuth(Request request) {
var pem = request.headers("ssl-client-cert"); ❶
var cert = decodeCert(pem); ❶
tentar {
if (cert.getSubjectAlternativeNames() == null) {
Retorna;
}
for (var san : cert.getSubjectAlternativeNames()) { ❷
if ((Integer) san.get(0) == DNS_TYPE) { ❷
var assunto = (String) san.get(1);
request.attribute("assunto", assunto); ❸
Retorna;
}
}
} catch (CertificateParsingException e) {
lança nova RuntimeException(e);
}
}

❶ Extraia o certificado do cliente do cabeçalho e decodifique-o.

❷ Localize a primeira entrada SAN com tipo de DNS.

❸ Defina a identidade da conta de serviço como o assunto da solicitação.

Para permitir que uma conta de serviço seja autenticada usando um certificado
de cliente em vez de nome de usuário e senha, você pode adicionar um caso ao
UserController authenticate métodoque verifica se um certificado de cli-
ente foi fornecido. Você só deve confiar no certificado se o controlador de entrada
puder verificá-lo. Conforme mencionado na tabela 11.2, o NGINX define o cabeça-
lho ssl-client-verify ao valor SUCCESS se o certificado for válido e assinado
por uma CA confiável, portanto, você pode usá-lo para decidir se deve confiar no
certificado do cliente.
AVISO Se um cliente pode definir seus próprios ssl-client-verify e ssl-
client-cert cabeçalhos, eles podem ignorar a autenticação do certificado. Você
deve testar se o seu controlador de entrada remove esses cabeçalhos de todas as
solicitações recebidas. Se seu controlador de entrada oferece suporte ao uso de
nomes de cabeçalho personalizados, você pode reduzir o risco adicionando uma
string aleatória a eles, como ssl-client-cert-zOAGY18FHbAAljJV . Isso torna
mais difícil para um invasor adivinhar os nomes de cabeçalho corretos, mesmo
que a entrada seja acidentalmente configurada incorretamente.

Agora você pode ativar a autenticação de certificado de cliente atualizando o


authenticate métodopara verificar se há um certificado de cliente válido e ex-
trair o identificador de assunto dele. A Listagem 11.8 mostra as alterações neces-
sárias. Abra o arquivo UserController.java novamente, adicione as linhas destaca-
das em negrito da listagem ao authenticate método e salve suas alterações.

Listagem 11.8 Habilitando autenticação de certificado de cliente

public void authenticate(Solicitação de solicitação, Resposta de resposta) {


if ("SUCESSO".equals(request.headers("ssl-client-verify"))) { ❶
processClientCertificateAuth(request); ❶
retorno; ❶
}
var credenciais = getCredentials(pedido); ❷
if (credenciais == null) return;
var nome de usuário = credenciais[0];
var senha = credenciais[1];

var hash = database.findOptional(String.class,


"SELECT pw_hash FROM users WHERE user_id = ?", nome de usuário);

if (hash.isPresent() && SCryptUtil.check(senha, hash.get())) {


request.attribute("assunto", nome de usuário);

var groups = database.findAll(String.class,


"SELECT DISTINCT group_id FROM group_members" +
"WHERE user_id = ?", nome de usuário);
request.attribute("grupos", grupos);
}
}

❶ Se a autenticação do certificado for bem-sucedida, use o certificado fornecido.

❷ Caso contrário, use a autenticação baseada em senha existente.

Agora você pode recriar o serviço da API Natter executando

eval $(minikube docker-env)


mvn compilação limpa jib:dockerBuild
no diretório raiz do projeto Natter. Em seguida, reinicie a API Natter e o banco de
dados para obter as alterações, 3 executando:

kubectl rollout reiniciar implantação \


natter-api-deployment natter-database-deployment -n natter-api

Depois que os pods forem reiniciados (usando kubectl get pods -n nat-
ter-api para verificar), você pode registrar um novo usuário do serviço como se
fosse uma conta de usuário comum:

curl -H 'Tipo de conteúdo: aplicativo/json' \


-d '{"username":"testservice","password":"senha"}' \
https://api.natter.local/users

Mini projeto

Você ainda precisa fornecer uma senha fictícia para criar a conta de serviço e al-
guém pode fazer login usando essa senha se for fraca. Atualize o método registe-
rUser do UserController (e o esquema do banco de dados) para permitir que a se-
nha esteja ausente, caso em que a autenticação de senha é desativada. O repositó-
rio GitHub que acompanha o livro tem uma solução no capítulo final do capítulo
11.
Agora você pode usar mkcert para gerar um certificado de cliente para esta
conta, assinado pela mkcert CA raiz que você importou como o arquivo ca-se-
cret . Use a -client opção para mkcert para gerar um certificado de cliente e
especifique o nome de usuário da conta de serviço:

mkcert -client testservice

Isso irá gerar um novo certificado para autenticação do cliente no arquivo testser-
vice-client.pem, com a chave privada correspondente em testservice-client-
key.pem. Agora você pode fazer login usando o certificado do cliente para obter
um token de sessão:

curl -H 'Tipo de conteúdo: aplicativo/json' -d '{}' \


--key testservice-client-key.pem \ ❶
--cert testservice-client.pem \ ❷
https://api.natter.local/sessions

❶ Use a opção --key para especificar a chave privada.

❷ Forneça o certificado com --cert.

Como a autenticação de certificado TLS efetivamente autentica todas as solicita-


ções enviadas na mesma sessão TLS, pode ser mais eficiente para um cliente reu-
tilizar a mesma sessão TLS para muitas solicitações de API HTTP. Nesse caso, você
pode fazer sem autenticação baseada em token e apenas usar ocertificado.

questionário

7. Qual dos seguintes cabeçalhos é usado pelo controlador de entrada Nginx para
indicar se a autenticação do certificado do cliente foi bem-sucedida?
1. ssl-client-cert
2. ssl-client-verify
3. ssl-client-issuer-dn
4. ssl-client-subject-dn
5. ssl-client-naughty-or-nice

A resposta está no final do capítulo.

11.4.4 Usando uma malha de serviço

EmboraA autenticação do certificado TLS é muito segura, os certificados do cli-


ente ainda devem ser gerados e distribuídos aos clientes e renovados periodica-
mente quando expiram. Se a chave privada associada a um certificado puder ser
comprometida, você também precisará ter processos para lidar com a revogação
ou usar certificados de curta duração. Esses são os mesmos problemas discutidos
no capítulo 10 para certificados de servidor, que é um dos motivos pelos quais
você instalou uma malha de serviço para automatizar o tratamento da configura-
ção TLS dentro da rede na seção 10.3.2.
Para dar suporte às políticas de autorização de rede, a maioria das implementa-
ções de malha de serviço já implementam TLS mútuo e distribuem certificados de
servidor e cliente para os proxies de malha de serviço. Sempre que uma solicita-
ção de API é feita entre um cliente e um servidor dentro da malha de serviço, essa
solicitação é atualizada de forma transparente para TLS mútuo pelos proxies e
ambas as extremidades são autenticadas entre si com certificados TLS. Isso au-
menta a possibilidade de usar a malha de serviço para autenticar clientes de ser-
viço na própria API. Para que isso funcione, o proxy de malha de serviço precisa-
ria encaminhar os detalhes do certificado do cliente do proxy sidecar para o ser-
viço subjacente como um cabeçalho HTTP, assim como você configurou o contro-
lador de entrada para fazer. O Istio suporta isso por padrão desde a versão 1.1.0,
usando o X-Forwarded-Client-Cert cabeçalho, mas o Linkerd atualmente não
possui esse recurso.

Ao contrário do NGINX, que usa cabeçalhos separados para diferentes campos ex-
traídos do certificado do cliente, o Istio combina os campos em um único cabeça-
lho, como no exemplo a seguir: 4

x-forwarded-client-cert: By=http://frontend.lyft.com;Hash=
➥ 468ed33be74eee6556d90c0149c1309e9ba61d6425303443c0748a
➥ 02dd8de688;Subject="CN=Test Client,OU=Lyft,L=San
➥ Francisco,ST=CA,C =EUA"
Os campos de um único certificado são separados por ponto e vírgula, como no
exemplo. Os campos válidos são dados na tabela 11.3.
Tabela 11.3 Campos Istio X-Forwarded-Client-Cert

Campo Descrição

By O URI do proxy que está encaminhando os detalhes do


cliente.

Hash Um hash SHA-256 codificado em hexadecimal do certifi-


cado de cliente completo.

Cert O certificado do cliente no formato PEM codificado por


URL.

Chain A cadeia completa de certificados do cliente, no formato


PEM codificado por URL.

Subject O campo Subject DN como uma string entre aspas duplas.

URI Quaisquer entradas SAN do tipo URI do certificado do cli-


ente. Este campo pode ser repetido se houver várias
entradas.

DNS Quaisquer entradas SAN do tipo DNS. Este campo pode


ser repetido se houver mais de uma entrada SAN
correspondente.

O comportamento do Istio ao definir este cabeçalho não é configurável e depende


da versão do Istio que está sendo usada. A versão mais recente define os campos
By , Hash , , e quando eles estão presentes no certificado Subject do cliente
usado pelo proxy sidecar do Istio para mTLS. Os próprios certificados do Istio
usam uma entrada URI SAN para identificar clientes e servidores, usando um pa-
drão chamado SPIFFE (Secure Production Identity Framework for
Everyone URI DNS ), que fornece uma maneira de nomear serviços em ambientes
de microsserviços. A Figura 11.6 mostra os componentes de um identificador
SPIFFE, que consiste em um domínio de confiança e um caminho. No Istio, o iden-
tificador de carga de trabalho consiste no namespace Kubernetes e na conta de
serviço. O SPIFFE permite que os serviços do Kubernetes recebam IDs estáveis ​
que podem ser incluídos em um certificado sem a necessidade de publicar entra-
das de DNS para cada um; O Istio pode usar seu conhecimento dos metadados do
Kubernetes para garantir que o ID do SPIFFE corresponda ao serviço ao qual um
cliente está se conectando.
Figura 11.6 Um identificador SPIFFE consiste em um domínio confiável e um iden-
tificador de carga de trabalho. No Istio, o identificador de carga de trabalho é
composto pelo namespace e pela conta de serviço do serviço.

DEFINIÇÃO SPIFFEsignifica Secure Production Identity Framework for


Everyone e é um URI padrão para identificar serviços e cargas de trabalho em
execução em um cluster. Veja https://spiffe.io para mais informações.

OBSERVAÇÃO As identidades do Istio são baseadas em contas de serviço do


Kubernetes, que são distintas dos serviços. Por padrão, há apenas uma única
conta de serviço em cada namespace, compartilhada por todos os pods nesse na-
mespace. Consulte http://mng.bz/yrJG para obter instruções sobre como criar con-
tas de serviço separadas e associá-las aos seus pods.

O Istio também possui sua própria versão do controlador de entrada do Kuberne-


tes, na forma do Istio Gateway. O gateway permite o tráfego externo na malha de
serviço e também pode ser configurado para processar o tráfego de saída que sai
da malha de serviço. 5 O gateway também pode ser configurado para aceitar cer-
tificados de cliente TLS de clientes externos, caso em que também definirá o X-
Forwarded-Client-Cert cabeçalho(e retire-o de quaisquer solicitações recebi-
das). O gateway configura os mesmos campos que os proxies sidecar do Istio, mas
também configura o campo Cert com o certificado codificado completo.

Como uma solicitação pode passar por vários proxies secundários do Istio du-
rante o processamento, pode haver mais de um certificado de cliente envolvido.
Por exemplo, um cliente externo pode fazer uma solicitação HTTPS para o Istio
Gateway usando um certificado de cliente e essa solicitação é encaminhada para
um microsserviço pelo Istio mTLS. Nesse caso, o certificado do proxy sidecar do
Istio sobrescreveria o certificado apresentado pelo cliente real e o microsserviço
só veria a identidade do gateway no X-Forwarded-Client-Cert cabeçalho.
Para resolver esse problema, os proxies sidecar do Istio não substituem o cabeça-
lho, mas anexam os novos detalhes do certificado ao cabeçalho existente, separa-
dos por uma vírgula. O microsserviço veria um cabeçalho com vários detalhes de
certificado, como no exemplo a seguir:

X-Forwarded-Client-Cert: Por=https://gateway.example.org;
➥ Hash=0d352f0688d3a686e56a72852a217ae461a594ef22e54cb
➥ 551af5ca6d70951bc,By=spiffe://api.natter.local/ns/ ❶
➥ natter-api/sa/natter-api-service;Hash=b26f1f3a5408f7
➥ 61753f3c3136b472f35563e6dc32fefd1ef97d267c43bcfdd1

❶ A vírgula separa as duas entradas de certificado.


O certificado do cliente original apresentado ao gateway é a primeira entrada no
cabeçalho e o certificado apresentado pelo proxy sidecar do Istio é a segunda. O
próprio gateway removerá qualquer cabeçalho existente das solicitações de en-
trada, portanto, o comportamento de anexação é apenas para proxies sidecar in-
ternos. Os proxies sidecar também removem o cabeçalho de novas solicitações de
saída que se originam dentro da malha de serviço. Esses recursos permitem que
você use autenticação de certificado de cliente no Istio sem precisar gerar ou ge-
renciar seus próprios certificados. Dentro da malha de serviço, isso é totalmente
gerenciado pelo Istio, enquanto clientes externos podem ser emitidos com certifi-
cados usando um externoCA.

11.4.5 TLS Mútuo com OAuth2

OAuth2também pode oferecer suporte a mTLS para autenticação de cliente por


meio de uma nova especificação (RFC 8705 https://tools.ietf.org/html/rfc8705 ), que
também adiciona suporte para tokens de acesso vinculados a certificados, discuti-
dos na seção 11.4.6. Quando usado para autenticação de cliente, há dois modos
que podem ser usados:
Na autenticação de certificado autoassinado, o cliente registra um certificado
com o AS que é assinado por sua própria chave privada e não por uma CA. O
cliente se autentica no endpoint de token com seu certificado de cliente e o AS
verifica se ele corresponde exatamente ao certificado armazenado no perfil do
cliente. Para permitir que o certificado seja atualizado, o AS pode recuperar o
certificado como x5c declaraçãoem um JWK de um URL HTTPS registrado para
o cliente.
No PKI (infraestrutura de chave pública), o AS estabelece confiança no certifi-
cado do cliente por meio de um ou mais certificados de CA confiáveis. Isso per-
mite que o certificado do cliente seja emitido e reemitido de forma indepen-
dente sem a necessidade de atualizar o AS. A identidade do cliente corresponde
ao certificado por meio dos campos Subject DN ou SAN no certificado.

Ao contrário da autenticação de portador JWT, não há como usar mTLS para obter
um token de acesso para uma conta de serviço, mas um cliente pode obter um to-
ken de acesso usando a concessão de credenciais do cliente. Por exemplo, o se-
guinte comando curl pode ser usado para obter um token de acesso de um AS que
suporta autenticação de cliente mTLS:

curl -d 'grant_type=client_credentials&scope=create_space' \
-d 'client_id=test' \ ❶
--cert test-client.pem \ ❷
--key test-client-key.pem \ ❷
https://as.example.org/oauth2/access_token
❶ Especifique o client_id explicitamente.

❷ Autentique usando o certificado do cliente e a chave privada.

O client_id parâmetrodeve ser especificado explicitamente ao usar autentica-


ção de cliente mTLS, para que o AS possa determinar os certificados válidos para
esse cliente se estiver usando o método autoassinado.

Como alternativa, o cliente pode usar a autenticação de cliente mTLS em combi-


nação com o tipo de concessão JWT Bearer da seção 11.3.2 para obter um token de
acesso para uma conta de serviço enquanto se autentica usando o certificado do
cliente, como no seguinte exemplo curl, que pressupõe que o A asserção JWT já foi
criada e assinada na variável $JWT :

ondulação \
-d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \ ❶
-d "assertion=$JWT&scope=a+b+c&client_id=test" \ ❶
--cert test-client.pem \ ❷
--key test-client-key.pem \ ❷
https://as.example.org/oauth2/access_token

❶ Autorize o uso de um portador JWT para a conta de serviço.

❷ Autentique o cliente usando mTLS.


A combinação de autenticação de portador mTLS e JWT é muito poderosa, como
você verá mais adiante emseção 11.5.3.

11.4.6 Tokens de acesso vinculados a certificado

Alémsuportando a autenticação do cliente, a especificação OAuth2 mTLS também


descreve como o AS pode, opcionalmente, vincular um token de acesso ao certifi-
cado do cliente TLS quando ele é emitido, criando um token de acesso vinculado
ao certificado. O token de acesso pode ser usado para acessar uma API somente
quando o cliente se autentica na API usando o mesmo certificado de cliente e
chave privada. Isso faz com que o token de acesso não seja mais um simples token
de portador porque um invasor que rouba o token não pode usá-lo sem a chave
privada associada (que nunca sai do cliente).

DEFINIÇÃO Um token de acesso vinculado a certificado não pode ser usado, ex-
ceto em uma conexão TLS que tenha sido autenticada com o mesmo certificado de
cliente usado quando o token de acesso foi emitido.

Tokens de prova de posse

Os tokens de acesso vinculados a certificado são um exemplo detokens de prova


de posse (PoP), também conhecidos como tokens de titular da chave, em que o to-
ken não pode ser usado a menos que o cliente comprove a posse de uma chave se-
creta associada. OAuth 1 suportava tokens PoP usando assinatura de solicitação
HMAC, mas a complexidade de implementar isso corretamente foi um fator para
o recurso ser descartado na versão inicial do OAuth2. Várias tentativas foram fei-
tas para reviver a ideia, mas até agora, os tokens vinculados a certificados são a
única proposta que se tornou um padrão.

Embora os tokens de acesso vinculados a certificados sejam ótimos quando você


tem uma PKI funcionando, eles podem ser difíceis de implantar em alguns casos.
Eles funcionam mal em aplicativos de página única e outros aplicativos da web.
Esquemas alternativos de PoP estão sendo discutidos, como um esquema baseado
em JWT conhecido como DPoP ( https://tools.ietf.org/html/draft-fett-oauth-dpop-03
), mas eles ainda precisam ser amplamente adotados.
Para obter um token de acesso vinculado a certificado, o cliente simplesmente se
autentica no ponto de extremidade do token com o certificado do cliente ao obter
um token de acesso. Se o AS oferecer suporte ao recurso, ele associará um hash
SHA-256 do certificado do cliente ao token de acesso. A API que recebe um token
de acesso de um cliente pode verificar uma vinculação de certificado de duas
maneiras:

Se estiver usando o endpoint de introspecção de token (seção 7.4.1 do capítulo


7), o AS retornará um novo campo do formulário "cnf": { "x5t#S256":
"...hash..." } onde o hash é o hash do certificado codificado em
Base64url. A cnf reclamaçãocomunica uma chave de confirmação, e a
x5t#S256 parte é o método de confirmaçãosendo usado.
Se o token for um JWT, as mesmas informações serão incluídas nas declarações
JWT definidas como uma "cnf" declaração com o mesmo formato.
DEFINIÇÃO Uma chave de confirmaçãocomunica à API como ela pode verificar
uma restrição sobre quem pode usar um token de acesso. O cliente deve confir-
mar que tem acesso à chave privada correspondente usando o método de confir-
mação indicado. Para tokens de acesso vinculados a certificado, a chave de confir-
mação é um hash SHA-256 do certificado do cliente e o cliente confirma a posse
da chave privada autenticando conexões TLS para a API com o mesmo certificado.

A Figura 11.7 mostra o processo pelo qual uma API impõe um token de acesso vin-
culado a certificado usando introspecção de token. Quando o cliente acessa a API,
ele apresenta seu token de acesso normalmente. A API faz a introspecção do token
chamando o endpoint de introspecção do token AS (capítulo 7), que retornará a
cnf declaraçãojunto com os outros detalhes do token. A API pode então comparar
o valor de hash nesta declaração com o certificado do cliente associado à sessão
TLS do cliente.
Figura 11.7 Quando um cliente obtém um token de acesso vinculado a um certifi-
cado e o usa para acessar uma API, a API pode descobrir a vinculação do certifi-
cado usando a introspecção do token. A resposta de introspecção conterá uma
"cnf" declaração contendo um hash do certificado do cliente. A API pode compa-
rar o hash com o certificado que o cliente usou para autenticar a conexão TLS
com a API e rejeitar a solicitação se for diferente.

Em ambos os casos, a API pode verificar se o cliente foi autenticado com o mesmo
certificado comparando o hash com o certificado do cliente usado para autenticar
na camada TLS. A Listagem 11.9 mostra como calcular o hash do certificado, co-
nhecido como thumbprint nas especificações do JOSE, usando a
java.security.MessageDigest classeque você usou no capítulo 4. O hash
deve ser calculado sobre a codificação binária completa do certificado, que é o
que o certificate.getEncoded() métodoretorna. Abra o arquivo
OAuth2TokenStore.java em seu editor e adicione o thumbprint métododa
listagem.

DEFINIÇÃO Uma impressão digital do certificadoou impressão digitalé um


hash criptográfico dos bytes codificados do certificado.

Listagem 11.9 Calculando uma impressão digital de certificado

byte privado[] impressão digital (certificado X509Certificate) {


tentar {
var sha256 = MessageDigest.getInstance("SHA-256"); ❶
return sha256.digest(certificate.getEncoded()); ❷
} catch (Exceção e) {
lança nova RuntimeException(e);
}
}

❶ Use uma instância SHA-256 MessageDigest.

❷ Faça hash dos bytes de todo o certificado.

Para impor uma ligação de certificado em um token de acesso, você precisa verifi-
car a resposta de introspecção do token para um cnf campocontendo uma chave
de confirmação. A chave de confirmação é um objeto JSON cujos campos são os
métodos de confirmação e os valores são determinados por cada método. Per-
corra os métodos de confirmação necessários, conforme mostrado na Listagem
11.9, para garantir que todos sejam atendidos. Se algum não estiver satisfeito ou
sua API não entender nenhum dos métodos de confirmação, você deve rejeitar a
solicitação para que um cliente não possa acessar sua API sem que todas as restri-
ções sejam respeitadas.

DICA A especificação JWT para métodos de confirmação (RFC 7800, https://tools


.ietf.org/html/rfc7800 ) requer apenas a especificação de um único método de con-
firmação. Para robustez, você deve verificar outros métodos de confirmação e re-
jeitar a solicitação se houver algum que sua API não entenda.

A Listagem 11.9 mostra como impor uma restrição de token de acesso vinculado a
certificado verificando um x5t#S256 método de confirmação. Se uma correspon-
dência for encontrada, base64url decodifique o valor da chave de confirmação
para obter o hash esperado do certificado do cliente. Isso pode ser comparado
com o hash do certificado real que o cliente usou para autenticar na API. Neste
exemplo, a API está sendo executada por trás do controlador de entrada Nginx,
portanto, o certificado é extraído do ssl-client-cert cabeçalho.

ATENÇÃO Lembre-se de verificar o ssl-client-verify cabeçalho para ga-


rantir que a autenticação do certificado foi bem-sucedida; caso contrário, você
não deve confiar no certificado.

Se o cliente estiver conectado diretamente ao servidor da API Java, o certificado


estará disponível por meio de um atributo de solicitação:

var cert = (X509Certificate) request.attributes(


"javax.servlet.request.X509Certificate");

Você pode reutilizar o decodeCert métodode UserController para decodificar


o certificado do cabeçalho e comparar o hash da chave de confirmação com a im-
pressão digital do certificado usando o MessageDigest.isEqual método. Abra o
arquivo OAuth2TokenStore.java e atualize o processResponse métodopara im-
por tokens de acesso vinculados a certificado, conforme mostrado na listagem a
seguir.

Listagem 11.10 Verificando um token de acesso vinculado a certificado


private Opcional<Token> processResponse(resposta JSONObject,
Pedido originalRequest) {
var expiração = Instant.ofEpochSecond(response.getLong("exp"));
var assunto = resposta.getString("sub");

var confirmKey = response.optJSONObject("cnf"); ❶


if (confirmationKey != null) {
for (var method : confirmKey.keySet()) { ❷
if (!"x5t#S256".equals(method)) { ❸
throw new RuntimeException( ❸
"Método de confirmação desconhecido: " + method); ❸
} ❸
if (!"SUCESSO".equals( ❹
originalRequest.headers("ssl-client-verify"))) { ❹
return Optional.empty(); ❹
var esperadoHash = Base64url.decode( ❺
confirmKey.getString(método)); ❺
var cert = UserController.decodeCert( ❻
originalRequest.headers("ssl-client-cert")); ❻
var certHash = impressão digital(cert); ❻
if (!MessageDigest.isEqual(expectedHash, certHash)) { ❻
return Optional.empty(); ❻
} ❻
}
}
var token = new Token(expiração, assunto);
token.attributes.put("scope", response.getString("scope"));
token.attributes.put("client_id",
response.optString("client_id"));

return Opcional.of(token);
}

❶ Verifique se uma chave de confirmação está associada ao token.

❷ Percorra os métodos de confirmação para garantir que todos estejam satisfeitos.

❸ Se houver algum método de confirmação não reconhecido, rejeite a solicitação.

❹ Rejeitar a solicitação se nenhum certificado válido for fornecido.

❺ Extraia o hash esperado da chave de confirmação.

❻ Decodifique o certificado do cliente e compare o hash, rejeitando se não


corresponderem.

Um ponto importante a ser observado é que uma API pode verificar um token de
acesso vinculado a certificado puramente comparando os valores de hash e não
precisa validar cadeias de certificados, verificar restrições básicas ou mesmo ana-
lisar o certificado! 6 Isso ocorre porque a autoridade para executar a operação da
API vem do token de acesso e o certificado está sendo usado apenas para evitar
que esse token seja roubado e usado por um cliente mal-intencionado. Isso reduz
significativamente a complexidade do suporte à autenticação de certificado de cli-
ente para desenvolvedores de API. A validação correta de um certificado X.509 é
difícil e historicamente tem sido uma fonte de muitas vulnerabilidades. Você pode
desabilitar a verificação de CA no controlador de ingresso usando a
optional_no_ca opçãodiscutido na seção 11.4.2, porque a segurança dos tokens
de acesso vinculados a certificados depende apenas do cliente usar o mesmo certi-
ficado para acessar uma API que usou quando o token foi emitido, independente-
mente de quem emitiu esse certificado.

DICA O cliente pode até usar um certificado autoassinado que ele gera logo antes
de chamar o ponto final do token, eliminando a necessidade de uma CA para emi-
tir certificados de cliente.

No momento da redação deste artigo, apenas alguns fornecedores de AS oferecem


suporte a tokens de acesso vinculados a certificados, mas é provável que isso au-
mente, pois o padrão foi amplamente adotado no setor financeiro. O Apêndice A
contém instruções sobre como instalar uma versão de avaliação do ForgeRock Ac-
cess Management 6.5.2, que suportaapadrão.

Tokens vinculados a certificados e clientes públicos

Um aspecto interessante da especificação OAuth2 mTLS é que um cliente pode so-


licitar tokens de acesso vinculados a certificados, mesmo que não use mTLS para
autenticação de cliente. Na verdade, mesmo um cliente público sem nenhuma
credencial pode solicitar tokens vinculados a certificados! Isso pode ser muito útil
para atualizar a segurança de clientes públicos. Por exemplo, um aplicativo móvel
é um cliente público porque qualquer pessoa que fizer o download do aplicativo
pode descompilá-lo e extrair quaisquer credenciais incorporadas a ele. No en-
tanto, muitos telefones celulares agora vêm com armazenamento seguro no hard-
ware do telefone. Um aplicativo pode gerar uma chave privada e um certificado
autoassinado nesse armazenamento seguro quando inicializado e, em seguida,
apresentar esse certificado ao AS quando obtém um token de acesso para vincular
esse token à sua chave privada.
  

questionário

8. Qual das seguintes verificações uma API deve executar para impor um token de
acesso vinculado a certificado? Escolha todas as verificações essenciais.
1. Verifique se o certificado não expirou.
2. Verifique se o certificado não expirou.
3. Verifique as restrições básicas no certificado.
4. Verifique se o certificado não foi revogado.
5. Verifique se o certificado foi emitido por uma CA confiável.
6. Compare a chave de confirmação x5t#S256 com o SHA-256 do certificado que
o cliente usou ao se conectar.
9. Verdadeiro ou falso: um cliente pode obter tokens de acesso vinculados a certi-
ficado somente se também usar o certificado para autenticação de cliente.
As respostas estão no final do capítulo.

11.5 Gerenciando credenciais de serviço

SeSe você usar segredos de cliente, tokens de portador JWT ou certificados de cli-
ente TLS, o cliente precisará acessar algumas credenciais para autenticar em ou-
tros serviços ou recuperar um token de acesso para usar em chamadas de serviço
a serviço. Nesta seção, você aprenderá como distribuir credenciais para clientes
com segurança. O processo de distribuição, rotação e revogação de credenciais
para clientes de serviço é conhecido como gerenciamento de segredos. Onde os se-
gredos são chaves criptográficas, então é alternativamente conhecido como ge-
renciamento de chaves.

O GERENCIAMENTO DE SEGREDOS DE DEFINIÇÃO é o processo de


criação, distribuição, rotação e revogação de credenciais necessárias aos serviços
para acessar outros serviços. Gerenciamento de chavesrefere-se ao gerencia-
mento de segredos em que os segredos são chaves criptográficas.

11.5.1 Segredos do Kubernetes

você temjá usou o próprio mecanismo de gerenciamento de segredos do Kuberne-


tes no capítulo 10, conhecido simplesmente como segredos. Como outros recursos
no Kubernetes, os segredos têm um nome e residem em um namespace, junta-
mente com pods e serviços. Cada segredo nomeado pode ter qualquer número de
valores de segredo nomeado. Por exemplo, você pode ter um segredo para cre-
denciais de banco de dados contendo um nome de usuário e senha como campos
separados, conforme mostrado na Listagem 11.11. Assim como outros recursos no
Kubernetes, eles podem ser criados a partir de arquivos de configuração YAML.
Os valores secretos são codificados em Base64, permitindo que dados binários ar-
bitrários sejam incluídos. Esses valores foram criados usando os comandos UNIX
echo e Base64:

echo -n 'dbuser' | base64

DICA Lembre-se de usar a -n opçãoao comando echo para evitar que um carac-
tere de nova linha extra seja adicionado aos seus segredos.

AVISO A codificação Base64 não é criptografia. Não verifique arquivos YAML de


segredos diretamente em um repositório de código-fonte ou outro local onde pos-
sam ser lidos facilmente.

Listagem 11.11 Exemplo de segredo do Kubernetes

apiVersão: v1
tipo: Segredo ❶
metadados:
nome: db-password ❷
namespace: natter-api ❷
tipo: Opaco
dados:
nome de usuário: ZGJ1c2Vy ❸
senha: c2VrcmV0 ❸

❶ O campo tipo indica que é um segredo.

❷ Dê um nome e um namespace ao segredo.

❸ O segredo tem dois campos com valores codificados em Base64.

Você também pode definir segredos em tempo de execução usando kubectl .


Execute o seguinte comando para definir um segredo para o nome de usuário e a
senha do banco de dados da API Natter:

kubectl cria senha db genérica secreta -n natter-api \


--from-literal=nome de usuário=natter \
--from-literal=senha=senha

DICA O Kubernetes também pode criar segredos de arquivos usando a --from-


file =username.txt sintaxe. Isso evita que as credenciais fiquem visíveis no
histórico do shell do seu terminal. O segredo terá um campo chamado
username.txt com o conteúdo binário do arquivo.

O Kubernetes define três tipos de segredos:


Os mais gerais são segredos genéricos, que são conjuntos arbitrários de pares
chave-valor, como os campos de nome de usuário e senha na listagem 11.11 e
no exemplo anterior. O Kubernetes não executa nenhum processamento espe-
cial desses segredos e apenas os disponibiliza para seus pods.
Um segredo TLSconsiste em uma cadeia de certificados codificados por PEM
juntamente com uma chave privada. Você usou um segredo TLS no capítulo 10
para fornecer o certificado do servidor e a chave para o controlador de entrada
do Kubernetes. Use kubectl create secret tls paracrie um segredo TLS.
Um segredo de registro do Dockeré usado para fornecer credenciais do Kuber-
netes para acessar um registro de contêiner privado do Docker. Você usaria isso
se sua organização armazenasse todas as imagens em um registro privado em
vez de enviá-las para um registro público como o Docker Hub. Usar kubectl
create secret docker-registry .

Para seus próprios segredos específicos do aplicativo, você deve usar o tipo de se-
gredo genérico.

Depois de definir um segredo, você pode disponibilizá-lo para seus pods de duas
maneiras:
Como arquivos montados no sistema de arquivos dentro de seus pods. Por
exemplo, se você montou o segredo definido na listagem 11.11 sob o caminho
/etc/secrets/db , acabaria com dois arquivos dentro do seu pod:
/etc/secrets/db/username e /etc/secrets/db/password . Seu aplica-
tivo pode então ler esses arquivos para obter os valores secretos. O conteúdo
dos arquivos serão os valores secretos brutos, não os codificados em Base64 ar-
mazenados no YAML.
Como variáveis ​de ambiente que são passadas para seus processos de contêiner
quando eles são executados pela primeira vez. Em Java, você pode acessá-los
através do System.getenv(String name) métodoligar.

DICA Os segredos baseados em arquivo devem ter preferência sobre as variáveis


​de ambiente. É fácil ler o ambiente de um processo em execução usando kubectl
describe pod , e você não pode usar variáveis ​de ambiente para dados biná-
rios, como chaves. Os segredos baseados em arquivo também são atualizados
quando o segredo é alterado, enquanto as variáveis ​de ambiente só podem ser al-
teradas reiniciando o pod.

A Listagem 11.12 mostra como expor o nome de usuário e a senha do banco de da-
dos Natter para os pods na implementação da API Natter atualizando o arquivo
natter-api-deployment.yaml. Um volume secreto é definido na volumes seção da
especificação do pod, referenciando o segredo nomeado a ser exposto. Em uma
volumeMounts seçãopara o contêiner individual, você pode montar o volume se-
creto em um caminho específico no sistema de arquivos. As novas linhas são des-
tacadas em negrito.

Listagem 11.12 Expondo um segredo para um pod

apiVersão: apps/v1
tipo: Implantação
metadados:
nome: natter-api-deployment
namespace: natter-api
especificação:
seletor:
matchLabels:
app: natter-api
réplicas: 1
modelo:
metadados:
rótulos:
app: natter-api
especificação:
securityContext:
runAsNonRoot: verdadeiro
recipientes:
- nome: natter-api
imagem: apisecurityinaction/natter-api:latest
imagePullPolicy: Nunca
Volume Mounts:
- nome: db-password ❶
mountPath: "/etc/secrets/database" ❷
readOnly: verdadeiro
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: verdadeiro
capacidades:
derrubar:
- tudo
portas:
- containerPort: 4567
volumes:
- nome: db-senha ❸
segredo:
secretName: db-password ❹

❶ O nome do volumeMount deve corresponder ao nome do volume.

❷ Especifique um caminho de montagem dentro do contêiner.

❸ O nome do volumeMount deve corresponder ao nome do volume.

❹ Forneça o nome do segredo a ser exposto.

Agora você pode atualizar a Main classepara carregar o nome de usuário e a se-
nha do banco de dados desses arquivos secretos, em vez de codificá-los. A Lista-
gem 11.13 mostra o código atualizado no main métodopara inicializar a senha do
banco de dados dos arquivos secretos montados. Você precisará importar
java.nio.file.* na parte superior do arquivo. Abra o arquivo .java principal e
atualize o método de acordo com a listagem. As novas linhas são destacadas em
negrito.

Listagem 11.13 Carregando segredos do Kubernetes

var secretsPath = Paths.get("/etc/secrets/database"); ❶


var dbUsername = Files.readString(secretsPath.resolve("username")); ❶
var dbPassword = Files.readString(secretsPath.resolve("senha")); ❶

var jdbcUrl = "jdbc:h2:tcp://natter-database-service:9092/mem:natter";


var fonte de dados = JdbcConnectionPool.create(
jdbcUrl, dbUsername, dbPassword); ❷
createTables(datasource.getConnection());

❶ Carregar segredos como arquivos do sistema de arquivos.

❷ Use os valores secretos para inicializar a conexão JDBC.

Você pode reconstruir a imagem do Docker executando 7

mvn compilação limpa jib:dockerBuild


em seguida, recarregue a configuração de implantação para garantir que o se-
gredo seja montado:

kubectl apply -f kubernetes/natter-api-deployment.yaml

Por fim, você pode reiniciar o Minikube para obter as alterações mais recentes:

parada do minikube && início do minikube

Use kubectl get pods -n natter-api --watch para verificar se todos os


pods são inicializados corretamente após as alterações.

Gerenciando segredos do Kubernetes

Embora você possa tratar os segredos do Kubernetes como outras configurações e


armazená-los em seu sistema de controle de versão, isso não é uma coisa inteli-
gente a se fazer por vários motivos:

As credenciais devem ser mantidas em segredo e distribuídas ao menor nú-


mero possível de pessoas. Armazenar segredos em um repositório de código-
fonte os torna disponíveis para todos os desenvolvedores com acesso a esse re-
positório. Embora a criptografia possa ajudar, é fácil errar, especialmente com
ferramentas de linha de comando complexas, como o GPG.
Os segredos devem ser diferentes em cada ambiente no qual o serviço é im-
plantado; a senha do banco de dados deve ser diferente em um ambiente de de-
senvolvimento em comparação com seus ambientes de teste ou produção. Esse
é o requisito oposto ao código-fonte, que deve ser idêntico (ou próximo disso)
entre os ambientes.
Quase não há valor em poder visualizar o histórico dos segredos. Embora você
queira reverter a alteração mais recente em uma credencial se ela causar uma
interrupção, ninguém precisará reverter para a senha do banco de dados de
dois anos atrás. Se for cometido um erro na criptografia de um segredo difícil
de alterar, como uma chave de API para um serviço de terceiros, será difícil ex-
cluir completamente o valor exposto de um sistema de controle de versão
distribuído.

Uma solução melhor é gerenciar manualmente os segredos da linha de comando


ou usar um sistema de modelo para gerar segredos específicos para cada ambi-
ente. O Kubernetes oferece suporte a um sistema de modelos chamado Kustomize,
que pode gerar segredos por ambiente com base em modelos. Isso permite que o
modelo seja verificado no controle de versão, mas os segredos reais são adiciona-
dos durante uma etapa de implantação separada. Veja http://mng.bz/Mov7 para
mais detalhes.
SEGURANÇA DOS SEGREDOS DO KUBERNETES

Embora os segredos do Kubernetes sejam fáceis de usar e forneçam um nível de


separação entre credenciais confidenciais e outros códigos-fonte e dados de confi-
guração, eles apresentam algumas desvantagens do ponto de vista da segurança:

Os segredos são armazenados dentro de um banco de dados interno no Kuber-


netes, conhecido como etcd. Por padrão, o etcd não é criptografado, então qual-
quer pessoa que tenha acesso ao armazenamento de dados pode ler os valores
de todos os segredos. Você pode ativar a criptografia seguindo as instruções em
http://mng.bz/awZz .

AVISO A documentação oficial do Kubernetes lista aescbc como o método de


criptografia mais forte suportado. Este é um modo de criptografia não autenti-
cado e potencialmente vulnerável a ataques de padding oracle, como você se lem-
brará do capítulo 6. Você deve usar a opção de criptografia kms se puder, porque
todos os modos exceto kms armazenam a chave de criptografia junto com os da-
dos criptografados, fornecendo apenas segurança limitada. Essa foi uma das des-
cobertas da auditoria de segurança do Kubernetes realizada em 2019 (
https://github .com/trailofbits/audit-kubernetes ).

Qualquer pessoa com a capacidade de criar um pod em um namespace pode


usá-lo para ler o conteúdo de quaisquer segredos definidos nesse namespace.
Os administradores do sistema com acesso raiz aos nós podem recuperar todos
os segredos da API do Kubernetes.
Segredos no disco podem ser vulneráveis ​à exposição por passagem de cami-
nhoou exposição de arquivovulnerabilidades. Por exemplo, Ruby on Rails tinha
uma vulnerabilidade recente em seu sistema de modelo que permitia a um in-
vasor remoto visualizar o conteúdo de qualquer arquivo enviando HTTP espe-
cialmente criadocabeçalhos ( https://nvd.nist.gov/vuln/detail/CVE-2019-5418 ).

DEFINIÇÃO Uma exposição de arquivoA vulnerabilidade ocorre quando um in-


vasor pode induzir um servidor a revelar o conteúdo de arquivos em disco que
não devem ser acessados ​externamente. Uma vulnerabilidade de path traversal
ocorre quando um invasor pode enviar uma URL para um servidor da web que
faz com que ele forneça um arquivo que deveria ser privado. Por exemplo, um in-
vasor pode solicitar o arquivo /public/../../../etc/secrets/db-password. Essas vulnera-
bilidades podem revelar os segredos do Kubernetes aos invasores.

11.5.2 Serviços de gerenciamento de chaves e segredos

UmA alternativa aos segredos do Kubernetes é usar um serviço dedicado para for-
necer credenciais ao seu aplicativo. Os serviços de gerenciamento de segredos ar-
mazenam credenciais em um banco de dados criptografado e as disponibilizam
para serviços por HTTPS ou um protocolo seguro semelhante. Normalmente, o cli-
ente precisa de uma credencial inicial para acessar o serviço, como uma chave de
API ou certificado de cliente, que pode ser disponibilizado por meio de segredos
do Kubernetes ou mecanismo semelhante. Todos os outros segredos são recupera-
dos do serviço de gerenciamento de segredos. Embora isso não pareça mais se-
guro do que usar os segredos do Kubernetes diretamente, ele tem várias
vantagens:

O armazenamento dos segredos é criptografado por padrão, proporcionando


melhor proteção dos dados secretos em repouso.
O serviço de gerenciamento de segredos pode gerar e atualizar segredos auto-
maticamente e regularmente. Por exemplo, Hashicorp Vault (
https://www.vaultproject.io ) pode criar automaticamente usuários de banco de
dados de curta duração em tempo real, fornecendo um nome de usuário e se-
nha temporários. Após um período configurável, o Vault excluirá a conta nova-
mente. Isso pode ser útil para permitir que as tarefas diárias de administração
sejam executadas sem deixar uma conta altamente privilegiada habilitada o
tempo todo.
Controles de acesso refinados podem ser aplicados, garantindo que os serviços
tenham acesso apenas às credenciais de que precisam.
Todo o acesso aos segredos pode ser registrado, deixando uma trilha de audito-
ria. Isso pode ajudar a estabelecer o que aconteceu após uma violação, e os sis-
temas automatizados podem analisar esses logs e alertar se forem notados pe-
didos de acesso incomuns.

Quando as credenciais que estão sendo acessadas são chaves criptográficas, um


Key Management Service (KMS) pode ser usado. Um KMS, como os fornecidos pe-
los principais provedores de nuvem, armazena com segurança o material da
chave criptográfica. Em vez de expor esse material de chave diretamente, um cli-
ente de um KMS envia operações criptográficas para o KMS; por exemplo, solici-
tar que uma mensagem seja assinada com uma determinada chave. Isso garante
que as chaves confidenciais nunca sejam expostas diretamente e permite que
uma equipe de segurança centralize os serviços criptográficos, garantindo que to-
dos os aplicativos usem algoritmos aprovados.

DEFINIÇÃO Um Key Management Service (KMS) armazena chaves em nome de


aplicativos. Os clientes enviam solicitações para executar operações criptográficas
ao KMS, em vez de solicitar o próprio material da chave. Isso garante que as cha-
ves confidenciais nunca saiam do KMS.

Para reduzir a sobrecarga de chamar um KMS para criptografar ou descriptogra-


far grandes volumes de dados, uma técnica conhecida como criptografia de enve-
lope pode ser usada. O aplicativo gera uma chave AES aleatória e a usa para crip-
tografar os dados localmente. A chave AES local é conhecida comochave de cripto-
grafia de dados (DEK). A própria DEK é então criptografada usando o KMS. A DEK
criptografada pode então ser armazenada ou transmitida com segurança junta-
mente com os dados criptografados. Para descriptografar, o destinatário primeiro
descriptografa a DEK usando o KMS e, em seguida, usa a DEK para descriptogra-
far o restante dos dados.

DEFINIÇÃO Em criptografia de envelope, um aplicativo criptografa dados com


uma chave de criptografia de dados local(DEK). A DEK é criptografada (ou encap-
sulada) com umchave de criptografia chave(KEK) armazenado em um KMS ou ou-
tro serviço seguro. A própria KEK pode ser criptografada com outra KEK, criando
uma hierarquia de chaves.

Para gerenciamento de segredos e KMS, o cliente geralmente interage com o ser-


viço usando uma API REST. Atualmente, não há API padrão comum suportada por
todos os provedores. Alguns provedores de nuvem permitem acesso a um KMS
usando a API PKCS#11 padrão usada por módulos de segurança de hardware.
Você pode acessar uma API PKCS#11 em Java através doJava Cryptography Archi-
tecture, como se fosse um keystore local, conforme a Listagem 11.14. (Esta lista-
gem é apenas para mostrar a API; você não precisa digitá-la.) Java expõe um dis-
positivo PKCS#11, incluindo um remoto como um KMS, como um KeyStore obje-
tocom o tipo "PKCS11" . 8 Você pode carregar o keystore chamando o
load() método, fornecendo um InputStream argumento nulo(porque não há
arquivo de armazenamento de chave local para abrir) e passar a senha do KMS
ou outra credencial como o segundo argumento. Depois que o keystore PKCS#11
for carregado, você poderá carregar as chaves e usá-las para inicializar Signatu-
re e Cipher objetos como qualquer outra chave local. A diferença é que o
Key objeto retornado pelo keystore PKCS#11 não possui material de chave dentro
dele. Em vez disso, o Java encaminhará automaticamente as operações criptográ-
ficas para o KMS por meio da API PKCS#11.

DICA O provedor criptográfico PKCS#11 integrado do Java suporta apenas alguns


algoritmos, muitos dos quais são antigos e não são mais recomendados. Um forne-
cedor KMS pode oferecer seu próprio provedor com suporte para mais
algoritmos.

Listagem 11.14 Acessando um KMS por meio de PKCS#11

var keyStore = KeyStore.getInstance("PKCS11"); ❶


var keyStorePassword = "changeit".toCharArray(); ❶
keyStore.load(null, keyStorePassword); ❶

var signatureKey = (PrivateKey) keyStore.getKey("rsa-key", ❷


keyStorePassword); ❷

var assinatura = Signature.getInstance("SHA256WithRSA"); ❸


signature.initSign(signingKey); ❸
signature.update("Olá!".getBytes(UTF_8)); ❸
var sig = signature.sign(); ❸

❶ Carregue o keystore PKCS11 com a senha correta.

❷ Recupere um objeto de chave do keystore.

❸ Use a tecla para assinar uma mensagem.

PKCS#11 e módulos de segurança de hardware


PKCS#11, ou Padrão de Criptografia de Chave Pública 11, define uma API padrão
para interagir commódulos de segurança de hardware(HSMs). Um HSM é um dis-
positivo de hardware dedicado ao armazenamento seguro de chaves criptográfi-
cas. Os HSMs variam em tamanho, desde pequenas chaves USB que suportam
apenas algumas chaves, até HSMs de rede montados em rack que podem lidar
com milhares de solicitações por segundo (e custam dezenas de milhares de dóla-
res). Assim como um KMS, o material da chave normalmente não pode ser aces-
sado diretamente pelos clientes e, em vez disso, eles enviam solicitações cripto-
gráficas ao dispositivo após o login. A API definida pelo PKCS#11, conhecida como
Cryptoki, fornece operações na linguagem de programação C para efetuando login
no HSM, listando as chaves disponíveis e realizando operações criptográficas.

Ao contrário de um KMS puramente de software, um HSM é projetado para ofere-


cer proteção contra um invasor com acesso físico ao dispositivo. Por exemplo, o
circuito do HSM pode ser envolto em resina resistente com sensores embutidos
que podem detectar qualquer pessoa que tente adulterar o dispositivo, caso em
que a memória segura é apagada para evitar comprometimento. Os governos dos
EUA e do Canadá certificam a segurança física dos HSMs sob o programa de certi-
ficação FIPS 140-2, que oferece quatro níveis de segurança: os dispositivos certifi-
cados de nível 1 oferecem apenas proteção básica do material da chave, enquanto
o nível 4 oferece proteção contra uma ampla gama de danos físicos e ameaças am-
bientais. Por outro lado,O FIPS 140-2 oferece muito pouca validação da qualidade
da implementação dos algoritmos em execução no dispositivo, e alguns HSMs
apresentam falhas graves de segurança de software. Alguns provedores de KMS
em nuvem podem ser configurados para usar HSMs com certificação FIPS 140-2
para armazenamento de chaves, geralmente a um custo maior. No entanto, a mai-
oria desses serviços já está sendo executada em datacenters fisicamente protegi-
dos, de modo que a proteção física adicional geralmente é desnecessária.
Um KMS pode ser usado para criptografar credenciais que são distribuídas para
serviços usando segredos do Kubernetes. Isso fornece melhor proteção do que a
configuração padrão do Kubernetes e permite que o KMS seja usado para prote-
ger segredos que não são chaves criptográficas. Por exemplo, uma senha de cone-
xão de banco de dados pode ser criptografada com o KMS e, em seguida, a senha
criptografada é distribuída aos serviços como um segredo do Kubernetes. O apli-
cativo pode usar o KMS para descriptografar a senha após carregá-la dodisco.

questionário

10. Quais das opções a seguir são maneiras pelas quais um segredo do Kubernetes
pode ser exposto a pods?
1. como arquivos
2. Como soquetes
3. Como pipes nomeados
4. Como variáveis ​de ambiente
5. Como buffers de memória compartilhada
11. Qual é o nome do padrão que define uma API para falar com módulos de segu-
rança de hardware?
1. PKCS#1
2. PKCS#7
3. PKCE
4. PKCS#11
5. PKCS#12

As respostas estão no final do capítulo.


11.5.3 Evitando segredos de longa duração no disco

Emboraum KMS ou gerenciador de segredos pode ser usado para proteger segre-
dos contra roubo, o serviço precisará de uma credencial inicial para acessar o
próprio KMS. Embora os provedores de KMS em nuvem geralmente forneçam um
SDK que lida com isso de forma transparente para você, em muitos casos o SDK
está apenas lendo suas credenciais de um arquivo no sistema de arquivos ou de
outra fonte no ambiente em que o SDK está sendo executado. risco de um invasor
comprometer essas credenciais e usar o KMS para descriptografar os outros
segredos.

DICA Muitas vezes, você pode restringir um KMS para permitir que suas chaves
sejam usadas apenas por clientes que se conectam de uma nuvem privada
virtual(VPC) que você controla. Isso torna mais difícil para um invasor usar cre-
denciais comprometidas porque ele não pode se conectar diretamente ao KMS
pela Internet.

Uma solução para esse problema é usar tokens de curta duração para conceder
acesso ao KMS ou ao gerenciador de segredos. Em vez de implantar um nome de
usuário e senha ou outra credencial estática usando segredos do Kubernetes, você
pode gerar uma credencial temporária com um tempo de expiração curto. O apli-
cativo usa essa credencial para acessar o KMS ou o gerenciador de segredos na
inicialização e descriptografar os outros segredos necessários para operar. Se um
invasor posteriormente comprometer o token inicial, ele terá expirado e não po-
derá ser usado. Por exemplo, o Hashicorp Vault ( https://vaultproject.io ) oferece
suporte à geração de tokens com um tempo de expiração limitado que um cliente
pode usar para recuperar outros segredos do cofre.

CUIDADO As técnicas nesta seção são significativamente mais complexas do


que outras soluções. Você deve avaliar cuidadosamente o aumento da segurança
em relação ao seu modelo de ameaça antes de adotar essas abordagens.

Se você usa OAuth2 principalmente para acesso a outros serviços, pode implantar
um JWT de curta duração que o serviço pode usar para obter tokens de acesso
usando a concessão de portador JWT descrita na seção 11.3. Em vez de fornecer
aos clientes acesso direto à chave privada para criar seus próprios JWTs, um pro-
cesso controlador separado gera JWTs em seu nome e distribui esses tokens de
portador de curta duração para os pods que precisam deles. O cliente então usa o
tipo de concessão de portador JWT para trocar o JWT por um token de acesso de
vida mais longa (e, opcionalmente, um token de atualização também). Dessa
forma, o tipo de concessão de portador JWT pode ser usado para impor uma sepa-
ração de tarefas que permite que a chave privada seja mantida com segurança
longe dos pods que atendem às solicitações do usuário. Quando combinado com
tokens de acesso vinculados a certificado da seção 11.4.6,

O principal problema com as credenciais de curta duração é que o Kubernetes foi


projetado para ambientes altamente dinâmicos nos quais os pods vêm e vão, e no-
vas instâncias de serviço podem ser criadas para responder ao aumento da carga.
A solução é ter um registro de processo do controlador com o servidor da API do
Kubernetes e observar a criação de novos pods. O processo do controlador pode
então criar uma nova credencial temporária, como um JWT assinado novo, e im-
plantá-la no pod antes de iniciar. O processo do controlador tem acesso a creden-
ciais de longa duração, mas pode ser implantado em um namespace separado
com políticas de rede rígidas para reduzir o risco de ser comprometido, conforme
mostrado na figura 11.8.
Figura 11.8 Um processo controlador em execução em um namespace de plano de
controle separado pode se registrar na API do Kubernetes para observar novos
pods. Quando um novo pod é criado, o controlador usa sua chave privada para as-
sinar um JWT de curta duração, que então implanta no novo pod. O pod pode en-
tão trocar o JWT por um token de acesso ou outras credenciais de longa duração.

Uma implementação de qualidade de produção desse padrão está disponível, no-


vamente para o Hashicorp Vault, como o projeto de integração Boostport Kuber-
netes-Vault ( https://github.com/ Boostport/kubernetes-vault ). Este controlador
pode injetar segredos exclusivos em cada pod, permitindo que o pod se conecte ao
Vault para recuperar seus outros segredos. Como os segredos iniciais são exclusi-
vos de um pod, eles podem ser restritos para permitir apenas um único uso, após
o qual o token se torna inválido. Isso garante que a credencial seja válida pelo me-
nor tempo possível. Se um invasor de alguma forma conseguir comprometer o to-
ken antes que o pod o use, o pod falhará ruidosamente ao iniciar quando não con-
seguir se conectar ao Vault, fornecendo um sinal às equipes de segurança de que
algo incomum aconteceuocorreu.

11.5.4 Derivação de chave

UMAUma abordagem complementar para proteger a distribuição de segredos é


reduzir o número de segredos que seu aplicativo precisa em primeiro lugar. Uma
maneira de conseguir isso é derivar chaves criptográficas para diferentes propósi-
tos de uma única chave mestra, usando umfunção de derivação chave(KDF). Um
KDF pega a chave mestra e um argumento de contexto, que normalmente é uma
string, e retorna uma ou mais novas chaves, conforme mostrado na Figura 11.9.
Um argumento de contexto diferente resulta em chaves completamente diferen-
tes e cada chave é indistinguível de uma chave completamente aleatória para al-
guém que não conhece a chave mestra, tornando-as adequadas como chaves crip-
tográficas fortes.
Figura 11.9 Uma função de derivação de chave (KDF) usa uma chave mestra e
uma string de contexto como entradas e produz chaves derivadas como saídas.
Você pode derivar um número quase ilimitado de chaves fortes de uma única
chave mestra de alta entropia.

Se você se lembra do capítulo 9, os macaroons funcionam tratando a tag HMAC de


um token existente como uma chave ao adicionar uma nova advertência. Isso
funciona porque o HMAC é uma função pseudo-aleatória segura, o que significa
que suas saídas aparecem completamente aleatórias se você não souber a chave.
Isso é exatamente o que precisamos para construir um KDF e, de fato, o HMAC é
usado como base para um KDF amplamente usado chamado HKDF (KDF baseado
em HMAC, https://tools.ietf.org/html/rfc5869 ). O HKDF consiste em duas funções
relacionadas:

Extrato HKDFtoma como entrada uma entrada de alta entropia que pode não
ser adequada para uso direto como uma chave criptográfica e retorna uma
chave mestra HKDF. Esta função é útil em alguns protocolos criptográficos, mas
pode ser ignorada se você já tiver uma chave HMAC válida. Você não usará o
HKDF-Extract neste livro.
HKDF-Expandirpega a chave mestra e um contexto e produz uma chave de
saída de qualquer tamanho solicitado.

DEFINIÇÃO HKDF é um KDF baseado em HMAC baseado em um método de ex-


tração e expansão. A função de expansão pode ser usada sozinha para gerar cha-
ves de uma chave mestre HMAC.

A Listagem 11.15 mostra uma implementação de HKDF-Expand usando HMAC-


SHA-256. Para gerar a quantidade necessária de material de chave de saída, o
HKDF-Expand executa um loop. Cada iteração do loop executa o HMAC para pro-
duzir um bloco de material de chave de saída com as seguintes entradas:

1. A marca HMAC da última vez no loop, a menos que este seja o primeiro loop.
2. A string de contexto.
3. Um byte de contador de bloco, que começa em 1 e é incrementado a cada vez.
Com HMAC-SHA-256, cada iteração do loop gera 32 bytes de material de chave de
saída, portanto, você normalmente precisará apenas de um ou dois loops para ge-
rar uma chave grande o suficiente para a maioria dos algoritmos. Como o conta-
dor de bloco é um único byte e não pode ser 0, você só pode repetir no máximo
255 vezes, o que dá um tamanho máximo de chave de 8.160 bytes. Por fim, o ma-
terial da chave de saída é convertido em um Key objeto usando a
javax.crypto .spec.SecretKeySpec classe. Crie um novo arquivo denomi-
nado HKDF.java na pasta src/main/java/com/manning/apisecurityinaction com o
conteúdo do arquivo.

DICA Se a chave mestra residir em um HSM ou KMS, será muito mais eficiente
combinar as entradas em uma matriz de byte único em vez de fazer várias cha-
madas para o update() método.

Listagem 11.15 HKDF-Expandir

pacote com.manning.apisecurityinaction;

importar javax.crypto.Mac;
importar javax.crypto.spec.SecretKeySpec;
importar java.security.*;

importar estático java.nio.charset.StandardCharsets.UTF_8;


importar estático java.util.Objects.checkIndex;
classe pública HKDF {
public static Key expand(Key masterKey, String context,
int outputKeySize, algoritmo String)
lança GeneralSecurityException {
checkIndex(outputKeySize, 255*32); ❶

var hmac = Mac.getInstance("HmacSHA256"); ❷


hmac.init(masterKey); ❷

var output = new byte[outputKeySize];


var bloco = novo byte[0];
for (int i = 0; i < outputKeySize; i += 32) { ❸
hmac.update(block); ❹
hmac.update(context.getBytes(UTF_8)); ❺
hmac.update((byte) ((i / 32) + 1)); ❺
bloco = hmac.doFinal(); ❻
System.arraycopy(block, 0, output, i, ❻
Math.min(outputKeySize - i, 32)); ❻
}

return new SecretKeySpec(saída, algoritmo);


}
}

❶ Certifique-se de que o chamador não pediu muito material de chave.

❷ Inicialize o Mac com a chave mestra.


❸ Faça um loop até que o tamanho de saída solicitado seja gerado.

❹ Incluir o bloco de saída do último loop no novo HMAC.

❺ Inclua a string de contexto e o contador de blocos atual.

❻ Copie a nova tag HMAC para o próximo bloco de saída.

Agora você pode usar isso para gerar quantas chaves quiser a partir de uma
chave HMAC inicial. Por exemplo, você pode abrir o arquivo Main.java e substi-
tuir o código que carrega a chave de criptografia AES do armazenamento de cha-
ves pelo seguinte código derivado da chave HMAC, conforme mostrado na linha
em negrito aqui:

var macKey = keystore.getKey("hmac-key", "changeit".toCharArray());


var encKey = HKDF.expand(macKey, "chave de criptografia de token",
32, "AES");

AVISO Uma chave criptográfica deve ser usada para um único propósito. Se você
usar uma chave HMAC para derivação de chave, não deverá usá-la também para
assinar mensagens. Você pode usar HKDF para derivar uma segunda chave HMAC
a ser usada para assinatura.

Você pode gerar quase qualquer tipo de chave simétrica usando esse método, cer-
tificando-se de usar uma string de contexto distinta para cada chave diferente. Os
pares de chaves para criptografia de chave pública geralmente não podem ser ge-
rados dessa maneira, pois é necessário que as chaves tenham alguma estrutura
matemática que não esteja presente em uma chave aleatória derivada. No en-
tanto, a biblioteca Salty Coffee usada no capítulo 6 contém métodos para gerar pa-
res de chaves para criptografia de chave pública e para assinaturas digitais a par-
tir de uma semente de 32 bytes, que podem ser usados ​da seguinte forma:

var seed = HKDF.expand(macKey, "nacl-signing-key-seed", ❶


32, "NaCl"); ❶
var keyPair = Crypto.seedSigningKeyPair(seed.getEncoded()); ❷

❶ Use HKDF para gerar uma semente.

❷ Derive um par de chaves de assinatura da semente.

CUIDADO Os algoritmos usados ​pelo Salty Coffee, X25519 e Ed25519, são proje-
tados para permitir isso com segurança. O mesmo não acontece com outros
algoritmos.

Embora gerar um punhado de chaves a partir de uma chave mestra possa não pa-
recer uma grande economia, o valor real vem da capacidade de gerar chaves pro-
gramaticamente iguais em todos os servidores. Por exemplo, você pode incluir a
data atual na string de contexto e derivar automaticamente uma nova chave de
criptografia todos os dias sem precisar distribuir uma nova chave para cada servi-
dor. Se você incluir a string de contexto nos dados criptografados, por exemplo,
como o kid cabeçalhoem um JWT criptografado, você pode derivar rapidamente
a mesma chave sempre que precisar, sem armazenaranteriorchaves.

CATs do Facebook

Como você pode esperar, o Facebook precisa executar muitos serviços em produ-
ção com vários clientes conectados a cada serviço. Na grande escala em que estão
sendo executados, a criptografia de chave pública é considerada muito cara, mas
eles ainda querem usar autenticação forte entre clientes e serviços. Cada solicita-
ção e resposta entre um cliente e um serviço é autenticada com HMAC usando
uma chave exclusiva para esse par cliente-serviço. Esses tokens HMAC assinados
são conhecidos comoTokens de autenticação criptográfica, ou CATs, e são um
pouco como JWTs assinados.

Para evitar armazenar, distribuir e gerenciar milhares de chaves, o Facebook usa


derivação de chave pesadamente. Um serviço central de distribuição de chaves
armazena uma chave mestra. Clientes e serviços são autenticados no serviço de
distribuição de chaves para obter as chaves com base em suas identidades. A
chave para um serviço com o nome “AuthService” é calculada usando
KDF(masterKey, "AuthService") , enquanto a chave para um cliente cha-
mado “Test” para falar com o serviço de autenticação é calculada como
KDF(KDF(masterKey, "AuthService"), "Test") . Isso permite que o Face-
book gere rapidamente um número quase ilimitado de chaves de cliente e serviço
a partir de uma única chave mestra. Você pode ler mais sobre os CATs do Face-
book em https://eprint.iacr.org/2018/413 .
  

questionário

12. Qual função HKDF é usada para derivar chaves de uma chave mestra HMAC?
1. Extrato HKDF
2. HKDF-Expandir
3. HKDF-Extrusão
4. HKDF-Exumar
5. HKDF-Exfiltrado

A resposta está no final do capítulo.

11.6 Chamadas de API de serviço em resposta a solicitações de


usuários

Quandoum serviço faz uma chamada de API para outro serviço em resposta a
uma solicitação do usuário, mas usa suas próprias credenciais em vez das do
usuário, há uma oportunidade para ataques de vice confusos como os discutidos
no capítulo 9. Como as credenciais de serviço geralmente são mais privilegiadas
do que o normal usuários, um invasor pode conseguir induzir o serviço a execu-
tar ações maliciosas em seu nome.
Vulnerabilidade crítica do servidor de API do Kubernetes

Em 2018, o próprio projeto Kubernetes relatou uma vulnerabilidade crítica que


permite esse tipo de ataque de deputado confuso (
https://rancher.com/blog/2018/2018-12-04-k8s-cve/). No ataque, um usuário fez
uma solicitação inicial ao servidor da API do Kubernetes, que autenticou a solici-
tação e aplicou verificações de controle de acesso. Em seguida, ele fez sua própria
conexão com um serviço de back-end para atender à solicitação. Essa solicitação
de API para o serviço de back-end usou credenciais de conta de serviço Kuberne-
tes altamente privilegiadas, fornecendo acesso de nível de administrador a todo o
cluster. O invasor pode induzir o Kubernetes a deixar a conexão aberta, permi-
tindo que o invasor envie seus próprios comandos ao serviço de back-end usando
a conta de serviço. A configuração padrão permitia que até mesmo usuários não
autenticados explorassem a vulnerabilidade para executar quaisquer comandos
em servidores de back-end. Para piorar a situação, o log de auditoria do Kuberne-
tes filtrou todas as atividades das contas do sistema para que não houvesse vestí-
gios de que um ataque havia ocorrido.

Você pode evitar ataques de vice confusos em chamadas de serviço a serviço reali-
zadas em resposta a solicitações de usuários, garantindo que as decisões de con-
trole de acesso tomadas em serviços de back-end incluam o contexto da solicita-
ção original. A solução mais simples é que os serviços de front-end repassem o
nome de usuário ou outro identificador do usuário que fez a solicitação original.
O serviço de back-end pode tomar uma decisão de controle de acesso com base na
identidade desse usuário, em vez de apenas na identidade do serviço de chamada.
A autenticação serviço a serviço é usada para estabelecer que a solicitação vem de
uma fonte confiável (o serviço front-end) e a permissão para executar a ação é de-
terminada com base na identidade do usuário indicado na solicitação.

DICA Como você se lembra do capítulo 9, a segurança baseada em capacidade


pode ser usada para eliminar sistematicamente ataques confusos de deputados.
Se a autoridade para executar uma operação for encapsulada como um recurso,
isso poderá ser passado do usuário para todos os serviços de back-end envolvidos
na implementação dessa operação. A autoridade para executar uma operação
vem da capacidade, e não da identidade do serviço que faz a solicitação, portanto,
um invasor não pode solicitar uma operação para a qual não tem capacidade.

11.6.1 O padrão do token fantasma

Emborapassar o nome de usuário do usuário original é simples e pode evitar ata-


ques de deputado confusos, um serviço de front-end comprometido pode facil-
mente representar qualquer usuário simplesmente incluindo seu nome de usuá-
rio na solicitação. Uma alternativa seria passar o token originalmente apresen-
tado pelo usuário, como um token de acesso OAuth2 ou JWT. Isso permite que os
serviços de back-end verifiquem se o token é válido, mas ainda apresenta algu-
mas desvantagens:
Se o token de acesso exigir introspecção para verificar a validade, uma cha-
mada de rede para o AS deverá ser realizada em cada microsserviço envolvido
no processamento de uma solicitação. Isso pode adicionar muita sobrecarga e
atrasos adicionais.
Por outro lado, os microsserviços de back-end não têm como saber se um token
assinado de longa duração, como um JWT, foi revogado sem executar uma soli-
citação de introspecção.
Um microsserviço comprometido pode pegar o token do usuário e usá-lo para
acessar outros serviços, personificando efetivamente o usuário. Se as chamadas
de serviço ultrapassarem os limites de confiança, como quando são feitas cha-
madas para serviços externos, o risco de expor o token do usuário aumenta.

Os dois primeiros pontos podem ser abordados por meio de um padrão de im-
plantação OAuth2 implementado por alguns gateways de API, mostrados na fi-
gura 11.10. Nesse padrão, os usuários apresentam tokens de acesso de longa dura-
ção ao gateway da API, que executa uma chamada de introspecção de token para
o AS para garantir que o token seja válido e não tenha sido revogado. O gateway
de API então pega o conteúdo da resposta de introspecção, talvez aumentada com
informações adicionais sobre o usuário (como funções ou associações de grupo) e
produz um JWT de curta duração assinado com uma chave confiável por todos os
microsserviços por trás do gateway. O gateway então encaminha a solicitação
para os microsserviços de destino, substituindo o token de acesso original por
esse JWT de curta duração. Às vezes, isso é chamado de padrão de token fan-
tasma. Se uma assinatura de chave pública for usada para o JWT, os microsservi-
ços poderão validar o token, mas não criar seus próprios.

Figura 11.10 No padrão de token fantasma, um gateway de API examina os tokens


de acesso que chegam de clientes externos. Em seguida, ele substitui o token de
acesso por um JWT assinado de curta duração contendo as mesmas informações.
Os microsserviços podem então examinar o JWT sem precisar chamar o AS para
fazer uma introspecção.

DEFINIÇÃO No padrão de token fantasma, um token de acesso opaco de longa


duração é validado e substituído por um JWT assinado de curta duração em um
gateway de API. Os microsserviços por trás do gateway podem examinar o JWT
sem precisar executar uma solicitação de introspecção cara.

A vantagem do padrão de token fantasma é que os microsserviços por trás do


gateway não precisam realizar chamadas de introspecção de token. Como o JWT é
de curta duração, normalmente com um tempo de expiração medido em segun-
dos ou minutos no máximo, não há necessidade de esses microsserviços verifica-
rem a revogação. O gateway da API pode examinar a solicitação e reduzir o es-
copo e o público do JWT, limitando o dano que seria causado se algum microsser-
viço de back-end fosse comprometido. Em princípio, se o gateway precisar cha-
mar cinco microsserviços diferentes para atender a uma solicitação, ele poderá
criar cinco JWTs separados com escopo e público adequados a cada solicitação.
Isso garante que o princípio do menor privilégio seja respeitado e reduz o risco se
qualquer um desses serviços for comprometido, mas raramente é feito devido à
sobrecarga extra de criar novos JWTs,

DICA Uma viagem de ida e volta de rede dentro do mesmo datacenter leva no
mínimo 0,5 ms mais o tempo de processamento exigido pelo AS (que pode envol-
ver solicitações de rede de banco de dados). A verificação de uma assinatura de
chave pública varia de cerca de 1/10 desse tempo (RSA-2048 usando OpenSSL) a
aproximadamente 10 vezes mais (ECDSA P-521 usando o provedor SunEC do
Java). A verificação de uma assinatura também geralmente requer mais poder de
CPU do que fazer uma chamada de rede, o que pode afetar os custos.

O padrão de token fantasma é um bom equilíbrio entre os benefícios e os custos


dos tokens de acesso opacos em comparação com os formatos de token indepen-
dentes, como JWTs. Tokens autocontidos são escaláveis ​e evitam viagens de ida e
volta extras na rede, mas são difíceis de revogar, enquanto o oposto é verdadeiro
para os opacostokens.

PRINCÍPIO Prefira usar tokens de acesso opacos e introspecção de token


quando os tokens ultrapassarem os limites de confiança para garantir a revoga-
ção oportuna. Use tokens independentes de curta duração para chamadas de ser-
viço dentro de um limite de confiança, como entre microsserviços.

11.6.2 Troca de token OAuth2

oA extensão de troca de token de OAuth2 ( https://www.rfc-


editor.org/rfc/rfc8693.html ) fornece uma maneira padrão para um gateway de
API ou outro cliente trocar um token de acesso por um JWT ou outro token de se-
gurança. Além de permitir que o cliente solicite um novo token, o AS também
pode adicionar uma act reivindicaçãoao token resultante que indica que o cli-
ente do serviço está agindo em nome do usuário identificado como o assunto do
token. Um serviço de back-end pode identificar o cliente do serviço e o usuário
que iniciou a solicitação originalmente de um único token de acesso.

DEFINIÇÃO A troca de tokens deve ser usada principalmente para semântica


de delegação, em que uma parte age em nome de outra, mas ambas são clara-
mente identificadas. Também pode ser usado para representação, em que o ser-
viço de back-end não consegue dizer que outra parte está representando o usuá-
rio. Você deve preferir a delegação sempre que possível porque a representação
leva a logs de auditoria enganosos e perda de responsabilidade.

Para solicitar uma troca de token, o cliente faz uma solicitação HTTP POST para o
endpoint de token do AS, assim como para outras concessões de autorização. O
grant_type parâmetroé definido como urn:ietf:params:oauth:grant-
type:token-exchange , e o cliente passa um token representando a autoridade
inicial do usuário como subject_token parâmetro, com um
subject_token_type parâmetrodescrevendo o tipo de token (a troca de token
permite que vários tokens sejam usados, não apenas tokens de acesso). O cliente
se autentica no endpoint de token usando suas próprias credenciais e pode forne-
cer vários parâmetros opcionais mostrados na tabela 11.4. O AS tomará uma deci-
são de autorização com base nas informações fornecidas e na identidade do su-
jeito e do cliente e, em seguida, retornará um novo token de acesso ou rejeitará a
solicitação.
DICA Embora a troca de tokens seja destinada principalmente a clientes de ser-
viço, o actor_token parâmetropode referenciar outro usuário. Por exemplo,
você pode usar a troca de token para permitir que os administradores acessem
partes das contas de outros usuários sem fornecer a senha do usuário. Embora
isso possa ser feito, há implicações óbvias de privacidade para seus usuários.
Tabela 11.4 Parâmetros opcionais de troca de token

Parâmetro Descrição

resource O URI do serviço que o cliente pretende acessar


em nome do usuário.

audience O público-alvo do token. Essa é uma alternativa


ao resource parâmetro em que o identificador
do serviço de destino não é um URI.

scope O escopo desejado do novo token de acesso.

requested_token_type O tipo de token que o cliente deseja receber.

actor_token Um token que identifica a parte que está agindo


em nome do usuário. Se não for especificado, a
identidade do cliente será usada.

actor_token_type O tipo do actor_token parâmetro.

O requested_token_type atributo permite que o cliente solicite um tipo especí-


fico de token na resposta. O valor urn:ietf:params:oauth:token-
type:access_token indica que o cliente deseja um token de acesso, em qual-
quer formato de token que o AS preferir, enquanto
urn:ietf:params:oauth:token-type:jwt pode ser usado para solicitar um
JWT especificamente. Existem outros valores definidos na especificação, permi-
tindo que o cliente solicite outros tipos de token de segurança. Dessa forma, a
troca de token OAuth2 pode ser vista como uma forma limitada de serviço de to-
ken de segurança.

DEFINIÇÃO Um serviço de token de segurança(STS) é um serviço que pode con-


verter tokens de segurança de um formato para outro com base em políticas de
segurança. Um STS pode ser usado para conectar sistemas de segurança que espe-
ram diferentes formatos de token.

Quando um serviço de back-end examina o token de acesso trocado, ele pode ver
uma cadeia aninhada de act declarações, conforme mostrado na Listagem
11.15. Assim como outros tokens de acesso, a sub declaração indica o usuário em
cujo nome a solicitação está sendo feita. As decisões de controle de acesso sempre
devem ser tomadas principalmente com base no usuário indicado nesta reivindi-
cação. Outras declarações no token, como funções ou permissões, serão sobre esse
usuário. A primeira act reivindicaçãoindica o serviço de chamada que está
agindo em nome do usuário. Uma act declaração é em si um conjunto de decla-
rações JSON que pode conter vários atributos de identidade sobre o serviço de
chamada, como o emissor de sua identidade, que pode ser necessário para identi-
ficar exclusivamente o serviço. Se o token passou por vários serviços, pode haver
outras act declarações aninhadas dentro da primeira, indicando os serviços an-
teriores que também atuaram como o mesmo usuário ao atender a mesma solici-
tação. Se o serviço de back-end quiser levar em consideração a conta de serviço
ao tomar decisões de controle de acesso, ele deve limitar isso apenas à primeira
act identidade (mais externa). Quaisquer act identidades anteriores destinam-
se apenas a garantir um registro de auditoria completo.

NOTA Declarações aninhadas act não indicam que service77 está fingindo ser
service16 fingindo ser Alice! Pense nisso como uma máscara sendo passada de
ator para ator, em vez de um único ator usando várias camadas de máscaras.

Listagem 11.16 Uma resposta de introspecção de token de acesso trocado

{
"aud":"https://service26.example.com",
"iss":"https://issuer.example.com",
"exp":1443904100,
"nbf":1443904000,
"sub":"[email protected]", ❶
"act": ❷
{
"sub":"https://service16.example.com", ❷
"agir": ❸
{
"sub":"https://service77.example.com" ❸
}
}
}

❶ O usuário efetivo do token

❷ O serviço que está agindo em nome do usuário

❸ Um serviço anterior que também atuou em nome do usuário na mesma solicitação

A troca de token introduz uma viagem de ida e volta de rede adicional ao AS para
trocar o token de acesso a cada salto de atendimento a uma solicitação. Portanto,
pode ser mais caro que o padrão de token fantasma e introduzir latência adicio-
nal em uma arquitetura de microsserviços. A troca de tokens é mais atraente
quando as chamadas de serviço ultrapassam os limites de confiança e a latência é
menos preocupante. Por exemplo, na área da saúde, um paciente pode entrar no
sistema de saúde e ser tratado por vários profissionais de saúde, cada um dos
quais precisa de algum nível de acesso aos registros do paciente. A troca de token
permite que um provedor entregue o acesso a outro provedor sem pedir repetida-
mente o consentimento do paciente. O AS decide um nível de acesso apropriado
para cada serviço com base nas políticas de autorização configuradas.

OBSERVAÇÃO Quando vários clientes e organizações recebem acesso aos da-


dos do usuário com base em um único fluxo de consentimento, você deve garantir
que isso seja indicado ao usuário na tela de consentimento inicial para que ele
possa tomar uma decisão informada.
Macaroons para APIs de serviço

Se o escopo ou a autoridade de um token só precisa ser reduzido ao chamar ou-


tros serviços, um token de acesso baseado em macaroon (capítulo 9) pode ser
usado como uma alternativa à troca de token. Lembre-se de que um biscoito per-
mite que qualquer parte acrescente ressalvas ao token, restringindo para que ele
pode ser usado. Por exemplo, um token inicial de escopo amplo fornecido por um
usuário que concede acesso aos registros de seus pacientes pode ser restringido
com ressalvas antes de ligar para serviços externos, talvez apenas para permitir o
acesso às anotações das últimas 24 horas. A vantagem é que isso pode ser feito lo-
calmente (e com eficiência) sem precisar chamar o AS para trocar o token.

Um uso comum de credenciais de serviço é para uma API de front-end fazer cha-
madas para um banco de dados de back-end. A API de front-end geralmente pos-
sui um nome de usuário e senha que usa para se conectar, com privilégios para
executar uma ampla gama de operações. Se, em vez disso, o banco de dados
usasse macaroons para autorização, ele poderia emitir um macaroon ampla-
mente privilegiado para o serviço de front-end. O serviço de front-end pode ane-
xar advertências ao biscoito e reemiti-lo para seus próprios clientes de API e, por
fim, para os usuários. Por exemplo, pode anexar uma advertência user =
"mary" a um token emitido para Mary para que ela possa ler apenas seus pró-
prios dados e um tempo de expiração de 5 minutos. Esses tokens restritos podem
então ser passados ​de volta para o banco de dados, que pode aplicar as advertên-
cias. Esta foi a abordagem adotada pelo banco de dados Hyperdex (
http://mng.bz/gg1l). Muito poucos bancos de dados suportam macaroons hoje, mas
em uma arquitetura de microsserviço você pode usar as mesmas técnicas para
permitir um controle de acesso mais flexível e dinâmico.
  

questionário

13. No padrão de token fantasma, o token de acesso original é substituído por qual
dos seguintes?
1. um macaron
2. Uma declaração SAML
3. Um JWT assinado de curta duração
4. Um token de ID do OpenID Connect
5. Um token emitido por um AS interno
14. Na troca de token OAuth2, qual parâmetro é usado para comunicar um token
que representa o usuário em cujo nome o cliente está operando?
1. O scope parâmetro
2. O resource parâmetro
3. O audience parâmetro
4. O actor_token parâmetro
5. O subject_token parâmetro

As respostas estão no final do capítulo.


Respostas para perguntas do questionário

1. d e e. As chaves de API identificam serviços, organizações externas ou empre-


sas que precisam chamar sua API. Uma chave de API pode ter um tempo de ex-
piração longo ou nunca expirar, enquanto os tokens de usuário geralmente ex-
piram após minutos ou horas.
2. e.
3. e. As credenciais do cliente e a autenticação da conta de serviço podem usar os
mesmos mecanismos; o principal benefício de usar uma conta de serviço é que
os clientes geralmente são armazenados em um banco de dados privado ao
qual somente o AS tem acesso. As contas de serviço residem no mesmo reposi-
tório que outros usuários e, portanto, as APIs podem consultar detalhes de
identidade e associações de função/grupo.
4. c, d e e.
5. e. A mensagem CertificateRequest é enviada para solicitar a autenticação do
certificado do cliente. Se não for enviado pelo servidor, o cliente não poderá
usar um certificado.
6. c. O cliente assina todas as mensagens anteriores no handshake com a chave
privada. Isso evita que a mensagem seja reutilizada para um handshake
diferente.
7. b.
8. f. A única verificação necessária é comparar o hash do certificado. O AS executa
todas as outras verificações quando emite o token de acesso. Embora uma API
possa opcionalmente implementar verificações adicionais, elas não são neces-
sárias para segurança.
9. Falso. Um cliente pode solicitar tokens de acesso vinculados a certificado
mesmo que use um método de autenticação de cliente diferente. Mesmo um cli-
ente público pode solicitar tokens de acesso vinculados a certificados.
10. a e d.
11. d.
12. uma. HKDF-Expandir. O HKDF-Extract é usado para converter material de
chave de entrada não uniforme em uma chave mestre uniformemente
aleatória.
13. c.
14. e.

Resumo

As chaves de API costumam ser usadas para autenticar chamadas de API de


serviço a serviço. Um JWT assinado ou criptografado é uma chave de API eficaz.
Quando usado para autenticar um cliente, isso é conhecido como autenticação
de portador JWT.
OAuth2 oferece suporte a chamadas de API de serviço a serviço por meio do
tipo de concessão de credenciais do cliente que permite que um cliente obtenha
um token de acesso sob sua própria autoridade.
Uma alternativa mais flexível para a concessão de credenciais do cliente é criar
contas de serviço que agem como contas de usuário comuns, mas destinadas ao
uso por serviços. As contas de serviço devem ser protegidas com mecanismos
de autenticação fortes porque geralmente têm privilégios elevados em compa-
ração com contas normais.
O tipo de concessão de portador JWT pode ser usado para obter um token de
acesso para uma conta de serviço usando um JWT. Isso pode ser usado para im-
plantar JWTs de curta duração em serviços quando eles são inicializados, que
podem ser trocados por tokens de acesso e atualização. Isso evita deixar cre-
denciais de longa duração e altamente privilegiadas no disco onde podem ser
acessadas.
Os certificados de cliente TLS podem ser usados ​para fornecer autenticação
forte de clientes de serviço. Os tokens de acesso vinculados a certificados me-
lhoram a segurança do OAuth2 e evitam roubo e uso indevido de tokens.
O Kubernetes inclui um método simples para distribuir credenciais para servi-
ços, mas sofre de algumas falhas de segurança. Cofres secretos e serviços de ge-
renciamento de chaves oferecem melhor segurança, mas precisam de uma cre-
dencial inicial para acessar. Um JWT de curta duração pode fornecer essa cre-
dencial inicial com o menor risco.
Quando chamadas de API de serviço a serviço são feitas em resposta a solicita-
ções de usuários, deve-se tomar cuidado para evitar ataques confusos de repre-
sentantes. Para evitar isso, a identidade do usuário original deve ser comuni-
cada aos serviços de back-end. O padrão de token fantasma fornece uma ma-
neira eficiente de conseguir isso em uma arquitetura de microsserviço, en-
quanto a troca de token OAuth2 e macaroons podem ser usados ​em confiançali-
mites.

1.
A autenticação OAuth2 Basic requer codificação de URL adicional se o ID ou se-
gredo do cliente contiver caracteres não ASCII. Consulte
https://tools.ietf.org/html/rfc6749#section-2.3.1 para obter detalhes.

2.
Existem subprotocolos adicionais que são usados ​para alterar algoritmos ou cha-
ves após o handshake inicial e para sinalizar alertas, mas você não precisa en-
tendê-los.

3.
O banco de dados deve ser reiniciado porque a API Natter tenta recriar o esquema
na inicialização e lançará uma exceção se já existir.

4.
O proxy sidecar do Istio é baseado no Envoy, desenvolvido pela Lyft, caso você es-
teja se perguntando sobre os exemplos!
5.
O Istio Gateway não é apenas um controlador de entrada do Kubernetes. Uma ma-
lha de serviço do Istio pode envolver apenas parte de um cluster do Kubernetes
ou pode abranger vários clusters do Kubernetes, enquanto um controlador de en-
trada do Kubernetes sempre lida com o tráfego externo que entra em um único
cluster.

6.
O código na Listagem 11.9 analisa o certificado como um efeito colateral da deco-
dificação do cabeçalho com um CertificateFactory, mas você pode evitar isso se
quiser.

7.
Lembre-se de executar eval $(minikube docker-env) se esta for uma nova
sessão de terminal.

8.
Se estiver usando o IBM JDK, use o nome “PKCS11IMPLKS”.

You might also like