HashiCorp Vault Secure Storage Backend in Apache APISIX Ecosystem

API7.ai

January 24, 2022

Ecosystem

Com o surgimento da arquitetura baseada em microsserviços, manter a segurança tornou-se muito mais desafiador do que antes. Estamos muito além do ponto em que nossas 100 instâncias de servidores backend acessam o servidor de banco de dados com uma única credencial secreta estática, porque, em caso de vazamento dessa credencial, todo o sistema é comprometido, e a revogação dessa credencial causa uma grande interrupção no serviço (agora ninguém pode acessar nada, a menos que as instâncias sejam reconfiguradas). Não podemos eliminar a possibilidade de uma violação de segurança, pois às vezes o inesperado acontece. Em vez disso, cabe a nós controlar o raio de explosão nessas situações. Para lidar com cenários como esse, uma solução popular como o HashiCorp Vault entra em cena em um ambiente de produção para atuar como um sistema de gerenciamento de segredos e criptografia baseado em identidade. Neste artigo, demonstrei como integrar o Vault com o Apache APISIX (um gateway de API nativo da nuvem) usando o plugin jwt-auth para aproveitar o melhor de ambos os mundos.

O que é o Vault

O HashiCorp Vault foi projetado para ajudar as organizações a gerenciar o acesso a segredos e transmiti-los com segurança dentro de uma organização. Segredos são definidos como qualquer forma de credencial sensível que precisa ser rigidamente controlada e monitorada e pode ser usada para desbloquear informações confidenciais. Segredos podem ser senhas, chaves de API, chaves SSH, tokens RSA ou OTP. No mundo real, é muito comum haver uma dispersão de segredos, onde eles acabam armazenados em arquivos de configuração ou como variáveis no código do programa, o que, como consequência, às vezes acaba em sistemas de controle de versão como GitHub, BitBucket ou GitLab, representando uma grande ameaça à segurança. O Vault resolve esse problema centralizando os segredos. Ele fornece armazenamento criptografado para segredos estáticos, geração de segredos dinâmicos com um prazo de validade (TTL), autenticação de usuários (máquinas ou humanos) para garantir que estejam autorizados a acessar um determinado segredo e muito mais. Assim, mesmo em caso de violação de segurança, o raio de explosão é muito menor e contido.

O Vault facilita muito o controle e o gerenciamento de acesso, fornecendo uma interface unilateral para gerenciar todos os segredos em sua infraestrutura. Além disso, ele também oferece a flexibilidade de criar logs de auditoria detalhados e rastrear quem acessou o quê.

HashiCorp Vault

Sobre o Plugin jwt-auth do APISIX

É um plugin de autenticação que pode ser anexado a qualquer rota do APISIX para realizar a autenticação JWT (JSON Web Token, saiba mais) antes que a solicitação seja encaminhada para o URI upstream. Em resumo, é um mecanismo de autenticação seguro que leva à autorização de recursos críticos. Normalmente, uma chave privada ou um segredo de texto é usado pelo emissor para assinar o JWT. O receptor do JWT verificará a assinatura para garantir que o token não foi alterado após ser assinado pelo emissor. A integridade total de todo o mecanismo JWT depende do segredo de assinatura (seja um segredo de texto ou pares de chaves RSA). Isso dificulta que fontes não autenticadas adivinhem a chave de assinatura e tentem alterar as reivindicações dentro do JWT.

Portanto, o armazenamento dessas chaves em um ambiente seguro é extremamente crucial. Cair em mãos erradas pode comprometer a segurança de toda a infraestrutura. Embora nós, da equipe do APISIX, tomemos todas as medidas para seguir as práticas padrão de SecOps, é bastante comum em ambientes de produção ter uma solução centralizada de gerenciamento de chaves, como o HashiCorp Vault, para ter trilhas de auditoria detalhadas, rotação periódica de chaves, revogação de chaves, etc. E seria bastante problemático se, a cada rotação de chave em toda a infraestrutura, você tivesse que atualizar a configuração do Apache APISIX.

Passos para Usar o Vault com o Apache APISIX

Para integração com o Vault, o Apache APISIX precisa ser carregado com a configuração do Vault no config.yaml.

Internamente, o APISIX se comunica com o servidor Vault usando o KV Secret Engine v1 através de APIs HTTP. Como a maioria das soluções empresariais prefere usar o KV Secrets Engine - Versão 1 em seus ambientes de produção, durante a fase inicial de suporte ao APISIX-Vault, optamos apenas pela versão 1. Em versões posteriores, adicionaremos suporte para a versão 2 do K/V.

A principal ideia de usar o Vault, em vez do backend etcd do APISIX, é a preocupação com a segurança em um ambiente de baixa confiança. Nós, desenvolvedores do APISIX, levamos suas prioridades a sério. É por isso que recomendamos o uso de tokens de acesso do Vault que têm escopo limitado e podem conceder ao servidor APISIX acesso restrito.

Configurar o Vault

Se você já tem uma instância do Vault em execução com os privilégios necessários, sinta-se à vontade para pular esta seção. Esta seção compartilha as melhores práticas para usar o Vault dentro do ecossistema do Apache APISIX. Siga os passos mencionados abaixo.

Passo 1: Iniciar um Servidor Vault

Aqui você tem várias opções, sinta-se à vontade para escolher entre Docker, binário pré-compilado ou compilação a partir do código-fonte. Como para se comunicar com o servidor Vault, você precisa de um cliente CLI do Vault, eu preferiria usar o binário pré-compilado em vez da abordagem Docker. De qualquer forma, a escolha é sua (consulte a documentação oficial de instalação do Vault). Para iniciar um servidor de desenvolvimento, execute o seguinte comando.

$ vault server -dev -dev-root-token-id=root
…
AVISO! O modo de desenvolvimento está ativado! Neste modo, o Vault é executado inteiramente na memória
e inicia desbloqueado com uma única chave de desbloqueio. O token raiz já está
autenticado no CLI, então você pode começar a usar o Vault imediatamente.
Você pode precisar definir a seguinte variável de ambiente:
export VAULT_ADDR='http://127.0.0.1:8200'
A chave de desbloqueio e o token raiz são exibidos abaixo, caso você queira
bloquear/desbloquear o Vault ou reautenticar.
Chave de Desbloqueio: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Token Raiz: root
O modo de desenvolvimento NÃO deve ser usado em instalações de produção!

Defina seu CLI atual com as variáveis de ambiente corretas.

export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'

Habilite o backend do mecanismo de segredos k/v versão 1 do Vault com um prefixo de caminho adequado. Nesta demonstração, vamos escolher o caminho kv para não termos colisão com o caminho de segredo padrão do Vault para a versão 2 do kv.

$ vault secrets enable -path=kv -version=1 kv
Sucesso! O mecanismo de segredos kv foi habilitado em: kv/

# Para reconfirmar o status, execute
$ vault secrets list
Caminho          Tipo         Accessor              Descrição
---- ---- -------- -----------
cubbyhole/    cubbyhole    cubbyhole_4eeb394c    armazenamento de segredo privado por token
identity/     identity     identity_5ca6201e     armazenamento de identidade
kv/           kv           kv_92cd6d37           n/a
secret/       kv           kv_6dd46a53           armazenamento de segredo chave/valor
sys/          system       system_2045ddb1       endpoints do sistema usados para controle, política e depuração

Passo 2: Gerar um Token de Acesso do Vault para o APISIX

Este artigo trata do uso do Vault na perspectiva do plugin jwt-auth. Portanto, para um consumidor do APISIX (se você não estiver familiarizado com consumidores no ecossistema do APISIX, leia a documentação sobre Consumidor do Apache APISIX) com o nome de usuário jack, o plugin jwt-auth procura (se habilitado com a configuração do Vault) por segredo(s) em <vault.prefix dentro do config.yaml>/consumer/<consumer.username>/jwt-auth no armazenamento kv do Vault. Neste contexto, se você estiver atribuindo o namespace (caminho do Vault) kv/apisix como vault.prefix dentro do config.yaml para todas as recuperações de dados relacionadas ao APISIX, sugerimos que você crie uma política para o caminho kv/apisix/consumer/. O asterisco extra () no final garante que a política permita a leitura para qualquer caminho que tenha o prefixo kv/apisix/consumer.

Crie um arquivo de política em HashiCorp Configuration Language (HCL).

$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {
    capabilities = ["read"]
}
EOF

Aplique a política na instância do Vault.

$ vault policy write apisix-policy apisix-policy.hcl

Sucesso! Política carregada: apisix-policy

Gere um token com a política recém-definida que foi configurada com um limite de acesso pequeno.

$ vault token create -policy="apisix-policy"

Chave                  Valor
--- -----
token                s.KUWFVhIXgoRuQbbp3j1eMVGa
token_accessor       nPXT3q0mfZkLmhshfioOyx8L
token_duration       768h
token_renewable      true
token_policies       ["apisix-policy" "default"]
identity_policies    []
políticas             ["apisix-policy" "default"]

Nesta demonstração, s.KUWFVhIXgoRuQbbp3j1eMVGa é o seu token de acesso.

Adicionar Configuração do Vault ao Apache APISIX

Como discutido anteriormente, o Apache APISIX se comunica com a instância do Vault através das APIs HTTP do Vault. A configuração necessária deve ser adicionada ao config.yaml. Aqui está uma breve informação sobre os diferentes campos que você pode usar:

  • host: O endereço do host onde o servidor Vault está em execução.
  • timeout: Tempo limite HTTP para cada solicitação.
  • token: O token gerado da instância do Vault que pode conceder acesso para ler dados do Vault.
  • prefix: habilitar um prefixo permite uma melhor aplicação de políticas, geração de tokens com escopo limitado e controle rigoroso dos dados que podem ser acessados pelo APISIX. Prefixos válidos são (kv/apisix, secret, etc.)
vault:
  host: 'http://0.0.0.0:8200'
  timeout: 10
  token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
  prefix: 'kv/apisix'

Criar um Consumidor do APISIX

O APISIX tem uma abstração de nível de consumidor que vai de mãos dadas com cenários de autenticação. Para habilitar a autenticação para qualquer rota do APISIX, é necessário um consumidor com uma configuração adequada para esse tipo específico de serviço de autenticação. Somente então o APISIX pode encaminhar a solicitação para o URI upstream, realizando com sucesso a autenticação em relação à configuração do consumidor. O consumidor do APISIX tem dois campos - um é username (obrigatório) para identificar um consumidor dos outros, e outro é plugins que contém as configurações específicas do plugin do consumidor.

Aqui, neste artigo, criaremos um consumidor com o plugin jwt-auth. Ele realiza a autenticação JWT para a(s) rota(s) ou serviço(s) respectivo(s).

Para habilitar o jwt-auth com a configuração do Vault, faça uma solicitação para:

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "username": "jack",
    "plugins": {
        "jwt-auth": {
            "key": "test-key",
            "vault": {}
        }
    }
}'

Aqui, o plugin procura o segredo da chave dentro do caminho do Vault (<vault.prefix do conf.yaml>/consumer/jack/jwt-auth) para o consumidor jack mencionado na configuração do consumidor e o usa para assinatura e verificação JWT subsequentes. Se a chave não for encontrada no mesmo caminho, o plugin registra um erro e falha ao realizar a autenticação JWT.

Configurar um Servidor Upstream de Teste

Para testar o comportamento, você pode criar uma rota para um upstream (um manipulador simples de ping que retorna pong). Você pode configurá-lo com um servidor HTTP simples em Go.

// servidor upstream simples
package main

import "net/http"

func ping(w http.ResponseWriter, req *http.Request) {
    w.Write([]byte("secure/pong\n"))
}

func main() {
    http.HandleFunc("/secure/ping", ping)
    http.ListenAndServe(":9999", nil)
}

Criar uma Rota do APISIX com Autenticação Habilitada

Crie uma rota do APISIX com este servidor HTTP seguro de ping e o plugin de autenticação jwt-auth habilitado.

$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "plugins": {
        "jwt-auth": {}
    },
    "upstream": {
        "nodes": {
            "127.0.0.1:9999": 1
        },
        "type": "roundrobin"
    },
    "uri": "/secure/ping"
}'

Gerar Token do Plugin jwt-auth

Agora, assine um segredo JWT do APISIX que pode ser usado e passado para fazer solicitações à rota de proxy [http://localhost:9080/secure/ping](http://localhost:9080/secure/ping) para o servidor APISIX.

$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Data: Ter, 18 Jan 2022 07:50:57 GMT
Tipo de Conteúdo: text/plain; charset=utf-8
Transfer-Encoding: chunked
Conexão: keep-alive
Servidor: APISIX/2.11.0

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8

Na etapa anterior, se você vir algo como a mensagem falha ao assinar jwt, certifique-se de ter uma chave secreta armazenada no caminho kv/apisix/consumers/jack/jwt-auth do Vault.

# exemplo
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Sucesso! Dados escritos em: kv/apisix/consumer/jack/jwt-auth

Solicitar ao Servidor APISIX

Agora, faça uma solicitação ao proxy do APISIX para a rota /secure/ping. Após a validação bem-sucedida, ele encaminhará a solicitação para o nosso servidor HTTP em Go.

$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Tipo de Conteúdo: text/plain; charset=utf-8
Comprimento do Conteúdo: 12
Conexão: keep-alive
Data: Ter, 18 Jan 2022 08:00:04 GMT
Servidor: APISIX/2.11.0

secure/pong

Qualquer solicitação sem um JWT válido resultará em um erro HTTP 401 Unauthorized.

$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Data: Ter, 18 Jan 2022 08:00:33 GMT
Tipo de Conteúdo: text/plain; charset=utf-8
Transfer-Encoding: chunked
Conexão: keep-alive
Servidor: APISIX/2.11.0

{"message":"Token JWT ausente na solicitação"}

Diferentes Casos de Uso Onde o Vault Pode Ser Integrado com o Plugin jwt-auth do APISIX

O plugin jwt-auth do Apache APISIX pode ser configurado para buscar chaves secretas simples de texto, bem como pares de chaves públicas-privadas RS256 do armazenamento do Vault.

:::note Para a versão inicial deste suporte de integração, o plugin espera que o nome da chave dos segredos armazenados no caminho do Vault esteja entre [ secret, public_key, private_key] para usar a chave com sucesso. Em versões futuras, adicionaremos suporte para referenciar chaves com nomes personalizados. :::

  1. Você armazenou um segredo de assinatura HS256 no Vault e deseja usá-lo para assinatura e verificação JWT.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "jack",
        "plugins": {
            "jwt-auth": {
                "key": "key-1",
                "vault": {}
            }
        }
    }'
    

    Aqui, o plugin procura a chave secret dentro do caminho do Vault (<vault.prefix do conf.yaml>/consumer/jack/jwt-auth) para o consumidor jack mencionado na configuração do consumidor e a usa para assinatura e verificação JWT subsequentes. Se a chave não for encontrada no mesmo caminho, o plugin registra um erro e falha ao realizar a autenticação JWT.

  2. Pares de chaves RSA RS256, tanto a chave pública quanto a privada, estão armazenadas no Vault.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "jim",
        "plugins": {
            "jwt-auth": {
                "key": "rsa-keypair",
                "algorithm": "RS256",
                "vault": {}
            }
        }
    }'
    

    O plugin procura as chaves public_key e private_key dentro do caminho kv do Vault (<vault.prefix do conf.yaml>/consumer/jim/jwt-auth) para jim mencionado na configuração do plugin do Vault. Se não forem encontradas, a autenticação falha.

    Se você não tiver certeza de como armazenar chaves públicas e privadas no armazenamento kv do Vault, use este comando:

    # desde que seu diretório atual contenha os arquivos chamados "public.pem" e "private.pem"
    $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem
    Sucesso! Dados escritos em: kv/apisix/consumer/jim/jwt-auth
    
  3. Chave pública na configuração do consumidor, enquanto a chave privada está no Vault.

    $ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
        "username": "john",
        "plugins": {
            "jwt-auth": {
                "key": "user-key",
                "algorithm": "RS256",
                "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----"
                "vault": {}
            }
        }
    }'
    

    Este plugin usa a chave pública RSA da configuração do consumidor e usa a chave privada diretamente obtida do Vault.

Desabilitar o Vault do Plugin

Agora, para desabilitar a busca no Vault do plugin jwt-auth, simplesmente remova o objeto vazio do Vault da configuração do plugin do consumidor (neste caso, é jack). Isso fará com que o plugin JWT procure segredos de assinatura (tanto HS256/HS512 quanto pares de chaves RS512) na configuração do plugin para solicitações subsequentes à rota URI onde a configuração jwt-auth foi habilitada. Mesmo que você tenha a configuração do Vault habilitada no config.yaml do APISIX, nenhuma solicitação será enviada ao servidor do Vault.

Os plugins do APISIX são recarregados em tempo real, portanto, não há necessidade de reiniciar o APISIX.

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "username": "jack",
    "plugins": {
        "jwt-auth": {
            "key": "test-key",
            "secret": "my-secret-key"
        }
    }
}'

Resumo

Este artigo traz a você a próxima versão da integração Vault-Apache APISIX e detalhes relacionados.

Sinta-se à vontade para iniciar uma discussão no GitHub Discussions ou se comunicar via lista de e-mails.

Tags: