Resumo
O GuardianKey Auth Bastion (GKAB) requer um módulo integrador para gerenciar a criação e encerramento de sessões no sistema protegido.
Esta documentação detalha o processo de integração.
O GKAB interage com sistemas OAuth2/OIDC e, após autenticar um usuário, envia uma requisição POST contendo informações necessárias para criar uma sessão no sistema protegido. O endpoint do módulo integrador deve retornar os dados do token que será inserido no navegador do usuário, bem como a URL para redirecionamento após a autenticação.
O módulo integrador deve interagir com o sistema protegido para criar a sessão, o que pode ser feito usando sua API ou mesmo acesso direto ao banco ou armazenamento de sessões.
Requisitos para Integração
Para realizar a integração, o módulo integrador deve atender aos seguintes requisitos:
- Hospedagem: Deve estar hospedado em uma URL acessível pelo GuardianKey Controller (exemplo: http://meuservidor/qualquer_path). Essa URL deve ser configurada no GKAB.
- Validação de Requisições: O endpoint deve validar as requisições utilizando a chave enviada no cabeçalho (header) X-API-Key.
- Funções Disponíveis: O endpoint deve implementar duas funções principais:
- Login: Responsável por criar a sessão do usuário no sistema protegido.
- Logout: Responsável por encerrar a sessão do usuário no sistema protegido.
 
A seguir, são descritas as entradas e saídas esperadas para cada uma dessas funções.
Autenticação das Requisições
Todas as requisições enviadas pelo GuardianKey Controller incluem o cabeçalho X-API-Key, que contém o hash SHA256 da API Key configurada no Auth Group correspondente.
Para localizar a API Key associada ao seu Auth Group, siga os passos abaixo:
- Acesse o menu GK Auth Bastion → Settings → Auth Groups.
- Edite o Auth Group desejado.
- Localize o campo API Key (readonly) e copie o valor exibido.
O módulo integrador deve calcular o hash SHA256 dessa API Key para utilizá-lo como valor do cabeçalho X-API-Key. Veja o exemplo abaixo:
import hashlib
minha_api_key = "sua_api_key_aqui"
xapikey = hashlib.sha256(minha_api_key.encode()).hexdigest()
Certifique-se de que o valor gerado seja utilizado para validar as requisições recebidas pelo módulo integrador.
Processo de Login
Durante a etapa de login, o GuardianKey Controller envia uma requisição ao módulo integrador contendo os dados do usuário autenticado. O módulo integrador deve processar essas informações, que incluem a chave "action": "login" e os dados fornecidos pelo sistema OAuth2/OIDC configurado, para criar uma sessão no sistema protegido. Esse processo garante que o usuário tenha acesso ao ambiente desejado de forma segura, monitorada e controlada.
Exemplo de Requisição
Abaixo está um exemplo de payload enviado pelo GuardianKey Controller ao módulo integrador para a criação da sessão na aplicação protegida:
{
    "action": "login",
    "sub": "19dfab21-6eaa-4db5-bae6-c69225c2b22d",
    "email_verified": false,
    "name": "João Silva",
    "preferred_username": "11111111111",
    "given_name": "João",
    "family_name": "Silva",
    "email": "[email protected]",
    "id_token": "",
    "oauth2_token": {
        "access_token": "..-",
        "expires_in": 300,
        "refresh_expires_in": 1800,
        "refresh_token": "",
        "token_type": "Bearer",
        "id_token": "",
        "not-before-policy": 0,
        "session_state": "9f14ea07-ab00-40c4-99b7-2ab09b890f99",
        "scope": "openid profile email",
        "expires_at": 1747186699
    }
}
Resposta Esperada
O módulo integrador deve retornar os seguintes dados:
- Método de autenticação: Indica como o navegador do usuário será autenticado (ex.: via cookies).
- Nomes e valores dos cookies: Informações necessárias para autenticar a sessão do usuário.
- URL de redirecionamento: Caminho para onde o navegador do usuário será redirecionado após o login.
- Caminho dos cookies: Define o escopo dos cookies no navegador.
Exemplo de Resposta
{
    "method": "cookie",
    "cookie_name": ["cookie_session", "cookie_session2"],
    "token": ["<token_gerado>", "<valor>"],
    "next_url": "/home",
    "cookie_path": "/"
}
Observações
- O campo tokendeve conter os valores dos cookies, na mesma ordem que estão em "cookie_name".
- A URL de redirecionamento (next_url) deve apontar para a página inicial ou dashboard do sistema protegido.
Processo de Logout
Durante a etapa de logout, o GuardianKey Controller envia uma requisição ao módulo integrador contendo os dados do usuário autenticado. O módulo integrador deve processar essas informações, que incluem a chave "action": "logout" e os dados fornecidos pelo sistema OAuth2/OIDC configurado, para encerrar a sessão no sistema protegido. Esse processo garante que o acesso do usuário ao ambiente seja revogado de forma segura e controlada.
Exemplo de Requisição
Abaixo está um exemplo de payload enviado pelo GuardianKey Controller ao módulo integrador para o encerramento da sessão na aplicação protegida:
{
    "action": "logout",
    "id_token": "xxx",
    "sub": "19dfab21-6eaa-4db5-bae6-c69225c2b22d",
    "preferred_username": "joao.silva",
    "name": "João Silva",
    "login_data": {
        "method": "cookie",
        "cookie_name": ["cookie_session", "cookie_session2"],
        "token": ["<token_gerado>", "<valor>"],
        "next_url": "/home",
        "cookie_path": "/"
    }
}
Resposta Esperada
O módulo integrador deve retornar os seguintes dados:
- Método de autenticação: Indica como o navegador do usuário será desautenticado (ex.: via cookies).
- Nomes e valores dos cookies: Informações necessárias para revogar a sessão do usuário.
- URL de redirecionamento: Caminho para onde o navegador do usuário será redirecionado após o logout.
Exemplo de Resposta
{
    "method": "cookie",
    "cookie_name": ["cookie_session", "cookie_session2"],
    "next_url": "/login"
}
Observações
- O campo cookie_namedeve conter os nomes dos cookies que precisam ser removidos ou invalidados.
- A URL de redirecionamento (next_url) deve apontar para a página de login ou outra página apropriada após o logout.
Exceções
Durante a integração, podem ocorrer exceções que devem ser tratadas adequadamente pelo módulo integrador. Abaixo está um exemplo de resposta JSON que o módulo integrador pode retornar em caso de erro, como uma chave de API inválida:
{
    "error": "Invalid API key. It should be the sha256 hash of the API key you found in the panel.",
    "received": "valor_recebido_no_cabecalho_X-API-Key",
    "next_url": "/login"
}
Observações
- O campo errordeve descrever o motivo do erro de forma clara.
- O campo receivedpode ser usado para depuração, indicando o valor recebido no cabeçalhoX-API-Key.
- O campo next_urldeve apontar para a página de login ou outra página apropriada para redirecionamento após o erro.
Código de exemplo para o integrador
from datetime import datetime
import hashlib
import sqlite3
from fastapi import FastAPI, Request
from pydantic import BaseModel
import os
secret_key = "SW2YcwTIb9zpOOhoPsMm"  # Replace with your actual secret key
xapikey = "xxxxxxx"  # Replace with your actual API key
app = FastAPI()
@app.get("/")
async def read_root():
    return {"message": "Bem-vindo à API FastAPI!"}
def get_user(username):
    conn = sqlite3.connect("/grafana/grafana.db")
    cursor = conn.cursor()
    cursor.execute("SELECT id,login,email FROM user WHERE login = ?", (username,))
    user = cursor.fetchone()
    conn.close()
    if user:
        return {
            "id": user[0],
            "login": user[1],
            "email": user[2]
        }
    else:
        return None
def delete_session_in_db(userdb,post_data):
    now = int(datetime.timestamp(datetime.now()))
    conn = sqlite3.connect("/grafana/grafana.db")
    cursor = conn.cursor()
    hashedToken = None
    try:
        if "login_data" in post_data:
            login_data = post_data["login_data"]
            unhashedToken = login_data["token"][0] if "token" in login_data and len(login_data["token"]) > 0 else None
            hashedToken = hashlib.sha256((unhashedToken + secret_key).encode()).hexdigest() if unhashedToken else None
    except:
        pass
    if hashedToken:
        cursor.execute(
            "UPDATE user_auth_token SET revoked_at = ? WHERE auth_token = ?",
            (now, hashedToken),
        )
    else:
        cursor.execute(
            "UPDATE user_auth_token SET revoked_at = ? WHERE user_id = ?",
            (now, userdb["id"]),
        )
    conn.commit()
    conn.close()
def create_session_in_db(userdb,post_data):
    user_agent = post_data["user_agent"] if "user_agent" in post_data else ""
    client_ip = post_data["client_ip"] if "client_ip" in post_data else ""
    now = int(datetime.timestamp(datetime.now()))
    conn = sqlite3.connect("/grafana/grafana.db")
    cursor = conn.cursor()
    unhashedToken = os.urandom(16).hex() 
    hashedToken = hashlib.sha256((unhashedToken + secret_key).encode()).hexdigest()
    cursor.execute(
        "INSERT INTO user_auth_token (user_id, auth_token, prev_auth_token, user_agent, client_ip, auth_token_seen, seen_at, rotated_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
        (userdb["id"], hashedToken, hashedToken, user_agent, client_ip, 0, now, now, now, now),
    )
    conn.commit()
    conn.close()
    
    return unhashedToken
    
@app.post("/create_session")
async def create_session(request: Request):
    if request.headers["X-API-Key"] != xapikey:
        return {
            "error": "Invalid API key. It should be the sha256 hash of the API key you found the the panel.",
            "received": request.headers["X-API-Key"],
            "next_url": "/login",
        }
    
    post_data = await request.json()
    if post_data["action"] == "login":
        username = post_data['preferred_username'] if 'preferred_username' in post_data else None
        userdb = get_user(username)
        if userdb == None:
            return {
                "error": "Usuário não encontrado",
                "received": post_data,
                "next_url": "/login",
            }
        token = create_session_in_db(userdb,post_data)
        timestamp_next_hour = int(datetime.timestamp(datetime.now())) + 3600
        return {
            "method": "cookie",
            "cookie_name": ["grafana_session", "grafana_session_expiry"],
            "token": [token, timestamp_next_hour],
            "next_url": "/dashboards",
            "cookie_path": "/",
        }
        
    elif post_data["action"] == "logout":
        username = post_data['preferred_username'] if 'preferred_username' in post_data else None
        userdb = get_user(username)
        delete_session_in_db(userdb,post_data)
        return {
            "method": "cookie",
            "cookie_name": ["grafana_session", "grafana_session_expiry"],
            "next_url": "/login",
        }
    else:
        return {"error": "Invalid action"}