À medida que avançamos em direção a uma mentalidade de confiança zero, torna-se clara a limitação de medidas de segurança grosseiras, como o sistema RBAC tradicional. Uma parte essencial da mudança para a confiança zero, que muitas vezes não é discutida, é a mudança de uma segurança grosseira para uma segurança refinada.
A autorização refinada resolve isso baseando o acesso em atributos como funções de usuário, ações e até mesmo contexto como hora ou local, e esse controle de acesso detalhado é vital para aplicativos modernos. Este artigo discute como
Com os recursos do ZITADEL, como funções, metadados e ações, os usuários podem obter controle de acesso altamente detalhado, adequado para uma configuração de confiança zero. Além disso, a ZITADEL pode trabalhar com serviços de autorização externa.
ZITADEL é um
Seus objetivos principais incluem fornecer recursos prontos para uso para autenticação, autorização, login e logon único (SSO), ao mesmo tempo que permite a personalização por meio de interfaces de usuário.
Ele vem com uma extensa trilha de auditoria para rastrear todas as alterações, permite que os desenvolvedores ampliem as funcionalidades com códigos personalizados (ações), suporta padrões amplamente reconhecidos como OIDC, OAuth, SAML e LDAP, enfatiza a facilidade de operação e escalabilidade e oferece APIs abrangentes para integração versátil.
ZITADEL usa RBAC para gerenciar permissões de usuários, onde as permissões estão vinculadas a funções e os usuários recebem essas funções. Isso simplifica o gerenciamento de acesso de usuários com base em suas funções organizacionais. Um recurso adicional permite que funções sejam delegadas a outras organizações, facilitando o compartilhamento de permissões com entidades externas.
Isto é especialmente valioso para organizações interconectadas ou hierárquicas.
Embora essas capacidades ofereçam um controle de acesso robusto, elas podem não ser suficientes para necessidades complexas de autorização, daí a importância de explorar a autorização detalhada no ZITADEL.
ZITADEL aprimora o RBAC tradicional introduzindo sua dinâmica
Com as ações da ZITADEL, podem ser criados scripts pós-autenticação para analisar atributos específicos do usuário e bloquear o acesso quando necessário.
As ações também podem estabelecer declarações personalizadas para impulsionar o sistema ABAC, permitindo modelos de autorização avançados que restringem o acesso com base em atributos como localização, hora ou qualquer fator definível.
ZITADEL permite que administradores ou desenvolvedores autorizados adicionem metadados personalizados a usuários e organizações, ampliando possibilidades de controle de acesso refinado.
Ele oferece suporte a reivindicações agregadas, coletando dados extras de sistemas externos, como ferramentas de CRM ou RH. A ZITADEL também pode gerenciar recursos exclusivos, como pedidos de remessa ou dispositivos IoT, e determinar o acesso com base em atributos como Subusuário, Funções, Reivindicações, IP e muito mais.
Apesar dos recursos abrangentes que acompanham o ZITADEL, pode haver casos em que seja necessária uma abordagem mais personalizada ou refinada.
Atualmente, a maneira mais eficaz de implementar autorização refinada no ZITADEL é usar lógica de aplicação personalizada para projetos menores ou para projetos de maior escala, aproveitando uma ferramenta de terceiros disponível, como warrant.dev , cerbos.dev , etc.
Essas ferramentas podem ser integradas ao ZITADEL, aumentando ainda mais sua capacidade de autorização diferenciada e refinada.
Digamos que haja um aplicativo hipotético de redação em uma empresa de mídia que se comunica com uma API de back-end. Os jornalistas usam-no para escrever, enquanto os editores editam e publicam esses artigos. Esta API, escrita em Python Flask neste exemplo, possui endpoints específicos e o acesso a esses endpoints depende da função do usuário e de sua experiência. Os pontos finais:
write_article
: Somente para jornalistas escreverem.
edit_article
: apenas para editores editarem artigos.
review_articles
: Para jornalistas seniores e editores intermediários e seniores revisarem artigos.
publish_article
: Para jornalistas intermediários e seniores e editores seniores publicarem. Internamente, a API utiliza um JWT emitido pela ZITADEL para verificar quem está fazendo as solicitações. Os usuários precisam enviar um JWT válido no cabeçalho da solicitação. Este JWT foi obtido quando o usuário efetuou login.
O JWT contém informações sobre o usuário, como sua função e experiência. Essas informações, contidas nas declarações personalizadas, são fundamentais para esse caso de uso. O backend decide se o usuário pode acessar o recurso solicitado com base nessas informações.
journalist
ou editor
. Isso é fundamental, pois define quem obtém qual acesso em nossa configuração. Gerenciando experiência/antiguidade: além das funções, a experiência do usuário (como junior
, intermediate
e senior
em nosso exemplo) é rastreada. Se a experiência de um usuário mudar, a ZITADEL a atualiza como metadados. Se não houver nenhum nível de experiência mencionado quando um usuário embarca no ZITADEL, o sistema apenas assume que é 'júnior'.
Separação de preocupações : No design desta API, foi dada atenção especial para garantir que a lógica de negócios e as regras de controle de acesso sejam claramente separadas. Isso é crucial para a manutenção e escalabilidade do aplicativo. Ao manter a lógica de negócios e as regras de acesso separadas, obtemos um design modular e mais limpo.
Isso nos permite atualizar ações comerciais e regras de acesso sem afetar uns aos outros. Isso aumenta a capacidade de manutenção do código e facilita o gerenciamento à medida que o aplicativo é dimensionado.
Além disso, esse design torna o sistema mais seguro, pois as regras de acesso são abstraídas da lógica de negócios principal, reduzindo o risco de introdução acidental de vulnerabilidades de segurança ao modificar a lógica de negócios.
Crie a organização Media House, vá em Projetos e crie um novo projeto chamado Newsroom.
No projeto Newsroom, clique no botão Novo para criar um novo aplicativo.
Vá para a guia Usuários em sua organização, conforme mostrado abaixo, e vá para a guia Usuários do serviço . Estaremos criando usuários de serviço nesta demonstração. Para adicionar um usuário de serviço, clique no botão Novo .
Em seguida, adicione os detalhes do usuário do serviço, selecione JWT para Tipo de token de acesso e clique em Criar .
Clique no botão Ações no canto superior direito. Selecione Gerar segredo do cliente no menu suspenso.
Copie seu ID do cliente e segredo do cliente. Clique em Fechar .
Agora você tem um usuário do serviço, juntamente com suas credenciais de cliente.
Vá para Autorizações . Clique em Novo .
Selecione o usuário e o projeto para o qual a autorização deve ser criada. Clique em Continuar .
Você pode selecionar uma função aqui. Selecione a função de jornalista para o usuário atual. Clique em Salvar .
Você pode ver que a usuária do serviço Lois Lane agora tem a função de jornalista no projeto Redação .
Agora, vamos adicionar metadados ao perfil do usuário para indicar seu nível de antiguidade. Use 'nível_de_experiência' como chave e, para seu valor, escolha entre 'júnior', 'intermediário' ou 'sênior'.
Embora normalmente possamos assumir que esses metadados são definidos por meio de uma chamada de API feita pelo aplicativo de RH, para simplificar e facilitar o entendimento, definiremos os metadados diretamente no console.
Vá para Metadados . Clique em Editar .
Forneça experience_level como chave e senior como valor. Clique no ícone salvar e clique no botão Fechar .
O usuário agora tem os metadados necessários associados à sua conta.
Você também pode adicionar mais alguns usuários de serviço com diferentes funções e níveis de experiência (usando metadados) para testar a demonstração usando as etapas anteriores.
1. Clique em Ações . Clique em Novo para criar uma nova ação.
2. Na seção Criar uma ação , atribua à ação o mesmo nome do nome da função, ou seja, atribuaRoleAndExperienceClaims. No campo de script, copie/cole o código a seguir e clique em Adicionar .
function assignRoleAndExperienceClaims(ctx, api) { // Check if grants and metadata exist if (!ctx.v1.user.grants || !ctx.v1.claims['urn:zitadel:iam:user:metadata']) { return; } // Decode experience level from Base64 - metadata is Base64 encoded let experience_encoded = ctx.v1.claims['urn:zitadel:iam:user:metadata'].experience_level; let experience = ''; try { experience = decodeURIComponent(escape(String.fromCharCode.apply(null, experience_encoded.split('').map(function(c) { return '0x' + ('0' + c.charCodeAt(0).toString(16)).slice(-2); })))); } catch (e) { return; // If decoding fails, stop executing the function } // Check if the experience level exists if (!experience) { return; } // Iterate through the user's grants ctx.v1.user.grants.grants.forEach(grant => { // Iterate through the roles of each grant grant.roles.forEach(role => { // Check if the user is a journalist if (role === 'journalist') { // Set custom claims with the user's role and experience level api.v1.claims.setClaim('journalist:experience_level', experience); } // Check if the user is an editor else if (role === 'editor') { // Set custom claims with the user's role and experience level api.v1.claims.setClaim('editor:experience_level', experience); } }); }); }
Agora, quando um usuário solicitar um token de acesso, a ação será executada, transformando as funções do usuário e os metadados no formato necessário e adicionando-os como uma declaração personalizada ao token. Essa declaração personalizada pode então ser usada por aplicativos de terceiros para gerenciar o acesso detalhado do usuário.
Clone o projeto do GitHub:
Execute o comando abaixo para clonar o projeto deste repositório GitHub:
git clone https://github.com/zitadel/example-fine-grained-authorization.git
Navegue até o diretório do projeto:
Após a clonagem, navegue até o diretório do projeto com
cd example-fine-grained-authorization
.
Configure um ambiente Python:
Certifique-se de ter o Python 3 e o pip instalados. Você pode verificar isso executando
python3 --version
epip3 --version
no seu terminal. Se você não possui Python ou pip instalados, você precisará instalá-los.
A seguir, crie um novo ambiente virtual para este projeto executando
python3 -m venv env
.
Ative o ambiente executando:
.\env\Scripts\activate
source env/bin/activate
Após executar este comando, seu terminal deverá indicar que agora você está trabalhando dentro do ambiente virtual env.
Instalar dependências:
Com o terminal no diretório do projeto (aquele que contém o arquivo requirements.txt), execute
pip3 install -r requirements.txt
para instalar as dependências necessárias.
Configurar variáveis de ambiente:
O projeto requer certas variáveis de ambiente. Preencha o arquivo .env
com os valores que recuperamos do ZITADEL.
PROJECT_ID="<YOUR PROJECT ID>" ZITADEL_DOMAIN="<YOUR INSTANCE DOMAIN eg https://instance-as23uy.zitadel.cloud>" ZITADEL_TOKEN_URL="<YOUR TOKEN URL eg https://instance-as23uy.zitadel.cloud/oauth/v2/token" CLIENT_ID="<YOUR SERVICE USER'S CLIENT ID FROM THE GENERATED CLIENT CREDENTIALS eg sj_Alice>" CLIENT_SECRET="<YOUR SERVICE USER'S SECRET FROM THE GENERATED CLIENT CREDENTIALS"> ZITADEL_INTROSPECTION_URL="<YOUR INTROSPECTION URL eg https://instance-as23uy.zitadel.cloud/oauth/v2/introspect>" API_CLIENT_ID="<THE CLIENT ID OF YOUR API APPLICATION FOR BASIC AUTH eg 324545668690006737@api>" API_CLIENT_SECRET="<THE CLIENT SECRET OF YOUR API APPLICATION FOR BASIC AUTH>"
Execute o aplicativo:
A API Flask (em app.py
) usa tokens JWT e declarações personalizadas para controle de acesso refinado. Ele verifica a reivindicação personalizada experience_level para as funções journalist
e editor
em cada solicitação, usando essas informações para decidir se o usuário autenticado pode acessar o endpoint solicitado.
aplicativo.py
from flask import Flask, jsonify from auth import token_required from access_control import authorize_access app = Flask(__name__) # Define the /write_article route. @app.route('/write_article', methods=['POST']) @token_required def write_article(): authorization = authorize_access('write_article') if authorization is not True: return authorization # Resource-specific code goes here... return jsonify({"message": "Article written successfully!"}), 200 # Define the /edit_article route. @app.route('/edit_article', methods=['PUT']) @token_required def edit_article(): authorization = authorize_access('edit_article') if authorization is not True: return authorization # Resource-specific code goes here... return jsonify({"message": "Article edited successfully!"}), 200 # Define the /review_article route. @app.route('/review_articles', methods=['GET']) @token_required def review_article(): authorization = authorize_access('review_article') if authorization is not True: return authorization # Resource-specific code goes here... return jsonify({"message": "Article review accessed successfully!"}), 200 # Define the /publish_article route. @app.route('/publish_article', methods=['POST']) @token_required def publish_article(): authorization = authorize_access('publish_article') if authorization is not True: return authorization # Resource-specific code goes here... return jsonify({"message": "Article published successfully!"}), 200 # Add more endpoints as needed... if __name__ == '__main__': app.run(debug=True)
auth.py
import os import jwt import requests from functools import wraps from flask import request, jsonify, g ZITADEL_INTROSPECTION_URL = os.getenv('ZITADEL_INTROSPECTION_URL') API_CLIENT_ID = os.getenv('API_CLIENT_ID') API_CLIENT_SECRET = os.getenv('API_CLIENT_SECRET') # This function checks the token introspection and populates the flask.g variable with the user's token def token_required(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get('Authorization') if not token: abort(401) # Return status code 401 for Unauthorized if there's no token else: token = token.split(' ')[1] # The token is in the format "Bearer <token>", we want to extract the actual token # Call the introspection endpoint introspection_response = requests.post( ZITADEL_INTROSPECTION_URL, auth=(API_CLIENT_ID, API_CLIENT_SECRET), data={'token': token} ) if not introspection_response.json().get('active', False): return jsonify({"message": "Invalid token"}), 403 # Decode the token and print it for inspection decoded_token = jwt.decode(token, options={"verify_signature": False}) print(f"\n\n***** Decoded Token: {decoded_token} \n\n******") # Add the decoded token to Flask's global context g.token = decoded_token return f(*args, **kwargs) return decorated
access_control.py (exemplo de código simulando um mecanismo de regras)
import base64 import jwt from flask import g, jsonify # The access_requirements dictionary represents your access control rules. access_requirements = { 'write_article': [{'role': 'journalist', 'experience_level': 'junior'}, {'role': 'journalist', 'experience_level': 'intermediate'}, {'role': 'journalist', 'experience_level': 'senior'}], 'edit_article': [{'role': 'editor', 'experience_level': 'junior'}, {'role': 'editor', 'experience_level': 'intermediate'}, {'role': 'editor', 'experience_level': 'senior'}], 'review_articles': [{'role': 'journalist', 'experience_level': 'senior'}, {'role': 'editor', 'experience_level': 'intermediate'}, {'role': 'editor', 'experience_level': 'senior'}], 'publish_article': [{'role': 'journalist', 'experience_level': 'intermediate'}, {'role': 'journalist', 'experience_level': 'senior'}, {'role': 'editor', 'experience_level': 'senior'}] # Add more endpoints as needed... } # This function checks if the user is authorized to access the given endpoint. def authorize_access(endpoint): # We assume that the token has already been decoded in auth.py decoded_token = g.token # Initialize role and experience_level variables role = None experience_level = None for claim, value in decoded_token.items(): if ':experience_level' in claim: role, _ = claim.split(':') experience_level = base64.b64decode(value).decode('utf-8') break # If there's no role in the token, return an error if not role: return jsonify({"message": "Missing role"}), 403 # If there's a role in the token but no experience level, default the experience level to 'junior' if role and not experience_level: experience_level = 'junior' # If there's no role or experience level in the token, return an error if not role or not experience_level: return jsonify({"message": "Missing role or experience level"}), 403 # Get the requirements for the requested endpoint endpoint_requirements = access_requirements.get(endpoint) # If the endpoint is not in the access control list, return an error if not endpoint_requirements: return jsonify({"message": "Endpoint not found in access control list"}), 403 # Check if the user's role and experience level meet the requirements for the requested endpoint for requirement in endpoint_requirements: required_role = requirement['role'] required_experience_level = requirement['experience_level'] # Experience level hierarchy experience_levels = ['junior', 'intermediate', 'senior'] if role == required_role and experience_levels.index(experience_level) >= experience_levels.index(required_experience_level): return True #return jsonify({"message": "Access denied"}), 403 return jsonify({"message": f"Access denied! You are a {experience_level} {role} and therefore cannot access {endpoint}"}), 403
Execute o aplicativo Flask executando:
python3 app.py
Se tudo estiver configurado corretamente, seu aplicativo Flask deverá estar em execução.
Este projeto foi desenvolvido e testado com Python 3, portanto, certifique-se de usar um interpretador Python 3.
Certifique-se de ter clonado o repositório e instalado as dependências necessárias conforme descrito anteriormente.
Execute o script client_credentials_token_generator.py
para gerar um token de acesso.
client_credentials_token_generator.py
import os import requests import base64 from dotenv import load_dotenv load_dotenv() ZITADEL_DOMAIN = os.getenv("ZITADEL_DOMAIN") CLIENT_ID = os.getenv("CLIENT_ID") CLIENT_SECRET = os.getenv("CLIENT_SECRET") ZITADEL_TOKEN_URL = os.getenv("ZITADEL_TOKEN_URL") PROJECT_ID = os.getenv("PROJECT_ID") # Encode the client ID and client secret in Base64 client_credentials = f"{CLIENT_ID}:{CLIENT_SECRET}".encode("utf-8") base64_client_credentials = base64.b64encode(client_credentials).decode("utf-8") # Request an OAuth token from ZITADEL headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": f"Basic {base64_client_credentials}" } data = { "grant_type": "client_credentials", "scope": f"openid profile email urn:zitadel:iam:org:project:id:{PROJECT_ID}:aud urn:zitadel:iam:org:projects:roles urn:zitadel:iam:user:metadata" } response = requests.post(ZITADEL_TOKEN_URL, headers=headers, data=data) if response.status_code == 200: access_token = response.json()["access_token"] print(f"Response: {response.json()}") print(f"Access token: {access_token}") else: print(f"Error: {response.status_code} - {response.text}")
Abra seu terminal e navegue até o diretório do projeto e execute o script usando python3:
python3 client_credentials_token_generator.py
Se for bem-sucedido, isso imprimirá um token de acesso ao seu terminal. Este é o token que você usará para autenticar suas solicitações na API.
Se você não iniciou a API Flask anteriormente, execute a API abrindo outro terminal no diretório do projeto e executando:
python3 app.py
O servidor API agora deve estar em execução e pronto para aceitar solicitações.
Agora você pode usar cURL ou qualquer outro cliente HTTP (como Postman) para fazer solicitações à API. Lembre-se de substituir your_access_token
nos comandos curl pelo token de acesso obtido na etapa 2.
Cenário 1: Editor Júnior Tenta Editar um Artigo (Sucesso)
O usuário com função editor
e nível de experiência junior
tenta chamar o endpoint edit_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/edit_article
Resultado esperado: {"message": "Article edited successfully"}
Cenário 2: Editor Júnior Tenta Publicar um Artigo (Falha)
O usuário com função editor
e nível de experiência junior
tenta chamar publish_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/publish_article
Resultado esperado: {"message": "Access denied! You are a junior editor and therefore cannot access publish_article"}
Cenário 3: Jornalista Sênior Tenta Escrever um Artigo (Sucesso)
O usuário com função journalist
e nível de experiência senior
tenta chamar o endpoint write_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/write_article
Resultado esperado: {"message": "Article written successfully"}
Cenário 4: Jornalista júnior tenta revisar artigos (falha)
Usuário com função journalist
e nível de experiência 'júnior' tenta chamar o endpoint review_articles
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/review_articles
Resultado esperado: {"message": "Access denied! You are a junior journalist and therefore cannot access review_articles"}
Cenário 5: Editor sênior tenta revisar artigos (sucesso)
O usuário com função editor
e nível de experiência senior
tenta acessar o endpoint review_articles
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/review_articles
Resultado esperado: {"message": "Article reviewed successfully"}
Cenário 6: Jornalista intermediário tenta publicar um artigo (sucesso)
O usuário com função journalist
e nível de experiência intermediate
tenta acessar publish_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/publish_article
{"message": "Article published successfully"}
Neste artigo, exploramos a importância de mudar do RBAC tradicional para uma abordagem de autorização mais detalhada e refinada usando ZITADEL.
Investigamos seus recursos, como ações dinâmicas para ABAC, a capacidade de integração com ferramentas de terceiros, e vimos como esses recursos podem ser aplicados na prática em um cenário do mundo real.
À medida que crescem as exigências da segurança cibernética, plataformas como a ZITADEL fornecem as soluções necessárias para desafios complexos de autorização.