Ir para o conteúdo

Embutir o Dashboard do Kibana

O Problema

Para oferecer um dashboard de acompanhamento de métricas e conversas, o Serprobots precisava expor as credenciais do Kibana e um link para o dashboard daquele ambiente:

  • Usabilidade ruim: é preciso sair da plataforma e fazer login manual.
  • Segurança comprometida: qualquer pessoa com esse link e essas credenciais acessa o dashboard (seja um desconhecido ou um usuário que deixou da estar na equipe do bot). Isso fica ainda mais crítico se pensarmos que o dashboard expoe o conteúdo das conversas (informação sensível).
  • Dashboard sem contexto: o dashboard do Kibana não apresenta o nome do chatbot com clareza no título ou em um de seus widgets, gerando o efeito: "será que esse dashboard é daquele chatbot mesmo?" Falta uma moldura visual para dar contexto.

Na solução desejada, espera-se que:

  • Não seja preciso expor as credenciais para o frontend, nem sequer trafegá-las no JSON de resposta.
  • Seja possível embutir o dashboard (ou pelo menos abri-lo numa nova aba sem necessidade de login manual)
  • O acesso ao dashboard só possa ser feito por um usuário que faça parte daquele chatbot naquele momento (autorização).

Histórico

No início do projeto Serprobots, havia o desejo de embutir o dashboard do Kibana na plataforma, mas isso ficou com prioridade baixa frente a outras necessidades. Mais tarde, fora dito que a plataforma não devería dar acesso ao Kibana, pois isso infringiria a licença de uso da ferramenta, o que abortou o assunto.

Uma solução através do Qlick Sense foi avaliada, mas esbarrou em dificuldades de sincronização de dados, já que essa ferramenta trabalha fazendo extrações, além de impossibilidades de fazer um controle adequado de permissão/autorização com separação de dados por chatbot.

Em conversas com a equipe que dá suporte ao Elastic no SERPRO em 2023, explicaram que o uso do Kibana para visualização, somente leitura, é permitido, resgatando o desejo de embutir o dashboard na plataforma.

Dashboard único e Separação de dados

A Elastic/Kibana oferece soluções para expor um dashboard publicamente, porém ela exige a criação de um método de autenticação anônimo que sempre usa um mesmo usuário para chegar no dashboard sem necessidade de informar senha numa tela de login. Se aplica para casos onde todos os usuário vão buscar a mesma informação no Elastic.

No caso do SERPROBOTS, apesar do objeto Dashboard ser um só, ele contempla dados diferentes conforme o usuário que o acessa. O dashboard funciona para todos pois ele observa os índices visíveis para aquele usuário no padrão log-* e vote-*. Cada chatbot tem seu respectivo usuário que enxerga seus respectivos índices de log de conversas e de votos (Ex: o bot fulano tem o usuário bot-fulano que só enxerga os índices log-bot-fulano e vote-bot-fulano.)

Dessa forma, para o Serprobots, é de suma importância que cada chatbot alcance o Kibana com uma credencial diferente, garantindo essa separação de dados.

A Solução

Em resumo, a solução alcançada foi: o acesso ao Kibana foi interceptado pelo backend do Serprobots, permitindo criar um proxy reverso onde é injetado o cabeçalho de chave Authorization e tendo como valor o Base64 do username:password do respectivo chatbot que se deseja acessar o dashboard.

Autenticação no Kibana

No Elastic versão comunitária, sem a subscrição, a única autenticação disponível é a http basic. Por sorte foi possível usá-la via cabeçalho Authorization.

Na versão subscrita, há prevista uma funcionalidade de geração de token de systema para estabelecer essa comunicação, e possivelmente seria uma solução mais correta (vide https://www.elastic.co/guide/en/elasticsearch/reference/current/token-authentication-services.html)

Para conseguir isso, foi preciso que o Kibana passasse a responder num base path compatível com o manager (/manager/api/kibana). Isso faz com que as chamadas à recursos estáticos do kibana (css, js, html, imagens, etc.) usem prefixo, e assim possam ser interceptadas pelo backend (vide ChatBotDashboardController) para que assim seja gerenciada o aspecto da autorização.

Configuração do Kibana:

Configuração de reescrita do basePath no kibana.yml:

server.basePath: "/manager/api/kibana"
server.rewriteBasePath: true

O acesso ao dashboard Kibana deve estar sujeito ao controle de autenticação e autorização da plataforma Serprobots. Porém, dois problemas ocorrem:

  1. Não há garantia de que o frontend irá enviar o cabeçalho de autorização com o token a cada request recebido, uma vez que os requests são oriundos do próprio kibana (talvez com a feature de acess token da subscrição).
  2. Mesmo usando soluções de contorno para receber o token de alguma forma, o desempenho degrada muito se for preciso fazer controle de autorização a cada requisição que passa pelo proxy reverso, consultando o banco para saber se o usuário X pertence ao chatbot Y e obtendo as credenciais de acesso ao Kibana desse chatbot.

A solução proposta foi o uso de um cookie. Antes de fazer acesso à URL do dashboard efetivamente, o frontend deve acessar o endpoint de dashboard de um determinado chatbot para um determinado ambiente:

/manager/api/chatbots/<CHATBOT_UUID>/environments/<ENVIRONMENT_TYPE_MNEMONIC>/dashboard

Esse endpoint vai acionar um caso de uso e checar toda a questão de segurança, autenticação e autorização no chatbot. Em seguida vai montar a URL do dashboard desejado, conforme variáveis de ambiente. Um cookie criptografado será setado (CURRENT_DASHBOARD_CONTEXT), válido por no máximo 12 horas e entregue apenas para o caminho do proxy reverso para o kibana nesse backend (/manager/api/kibana). Nesse cookie é indicando, dentre outros, qual usuário e senha deve ser usado para chegar no kibana. Por fim, esse endpoint retorna essa URL pro dashboard via proxy e pode forçar um redirect se desejado (opção ?redirect=true).

No frontend angular, basicamente é feito um acesso assíncrono ao endpoint de dashboard do chatbot+ambiente corrente, e obtendo como retorno a URL do dashboard, ela é usada como [src] um iFrame. Para isso funcionar, é preciso usar o Sanitizer (vide dashboard.component.ts):

this.embeddedDashboardURL = this.sanetize.bypassSecurityTrustResourceUrl(`${finalUrl}`);                

Outros assuntos úteis

Rodar o elastic com segurança

docker run -d --name elasticsearch --net elastic -p 9200:9200 -e "discovery.type=single-node" -e 'ES_JAVA_OPTS: -Xms2g -Xmx2g' -e "xpack.security.enabled=true" elasticsearch:7.17.8

Gerar senhas para os usuários

docker exec -it elasticsearch sh /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto

Alterar o kibana.yml para ter a credencial gerada.

Rodar o kibana

docker run --name kibana --net elastic -p 127.0.0.1:5601:5601 -v "$(pwd)"/kibana.yml:/usr/share/kibana/config/kibana.yml docker.elastic.co/kibana/kibana:7.17.8

SSL no Kibana

Kibana só aceita fazer login se tiver sendo acessado por SSL

Mandar o container elastic gerar o certificado:

docker exec -it elasticsearch sh /usr/share/elasticsearch/bin/elasticsearch-certutil csr -name kibana-server -dns localhost

Descompactar:

docker exec -it elasticsearch unzip /usr/share/elasticsearch/csr-bundle.zip

Copiar os arquivo pra fora do container:

docker exec -it elasticsearch cat /usr/share/elasticsearch/kibana-server/kibana-server.csr >> kibana-server.csr
docker exec -it elasticsearch cat /usr/share/elasticsearch/kibana-server/kibana-server.key >> kibana-server.key

Assinar o certificado com openssl pra gerar o arquivo crt:

openssl x509 -req -days 999 -in kibana-server.csr -signkey kibana-server.key -out kibana-server.crt

Adicionar ao kibana.yml o trecho:

server.ssl.enabled: true
server.ssl.certificate: /etc/kibana/certs/kibana-server.crt
server.ssl.key: /etc/kibana/certs/kibana-server.key

Rodar o kibana com o volume de certificados:

docker run --name kibana --net elastic -p 127.0.0.1:5601:5601 -v "$(pwd)"/kibana.yml:/usr/share/kibana/config/kibana.yml -v "$(pwd)"/certs:/etc/kibana/certs docker.elastic.co/kibana/kibana:7.17.8