11 Securing Service-To-service APIs - API Security in Action
11 Securing Service-To-service APIs - API Security in Action
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.
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.
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.
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.
$ curl -u test:password \ ❶
-d 'grant_type=client_credentials&scope=a+b+c' \ ❷
https://as.example.com/access_token
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.
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.
$ curl -u teste:senha \ ❶
-d 'grant_type=password&scope=a+b+c' \
-d 'username=serviceA&password=password' \ ❷
https://as.example.com/access_token
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.
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
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.
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.
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.
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.*;
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:
O resultado será um objeto JSON contendo um único keys campo, que é uma ma-
triz de JSON Web Keys.
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.
Depois que o fluxo do cliente for concluído, ele imprimirá a resposta do token de
acesso doCOMO.
urn:ietf:params:oauth:grant-type:jwt-bearer
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.
questionário
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
mkcert -CAROOT
/Users/neil/Library/Application Support/mkcert
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
Se você ainda tiver o Minikube em execução no capítulo 10, agora pode atualizar
a definição de entradacorrida:
Cabeçalho Descrição
importar java.io.ByteArrayInputStream;
importar java.net.URLDecoder;
importar java.security.cert.*;
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.
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.
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:
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:
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:
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
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
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
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.
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
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.
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.
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.
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.
return Opcional.of(token);
}
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.
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.
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.
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.
apiVersão: v1
tipo: Segredo ❶
metadados:
nome: db-password ❷
namespace: natter-api ❷
tipo: Opaco
dados:
nome de usuário: ZGJ1c2Vy ❸
senha: c2VrcmV0 ❸
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.
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.
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 ❹
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.
Por fim, você pode reiniciar o Minikube para obter as alterações mais recentes:
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:
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
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.
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,
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.
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.
pacote com.manning.apisecurityinaction;
importar javax.crypto.Mac;
importar javax.crypto.spec.SecretKeySpec;
importar java.security.*;
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:
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:
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.
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
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
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.
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.
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.
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
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.
{
"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" ❸
}
}
}
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.
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
Resumo
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”.