По мере того как мы движемся к мышлению с нулевым доверием, ограничения общих мер безопасности, таких как традиционная система RBAC, становятся очевидными. Существенной частью перехода к нулевому доверию, которая часто остается без обсуждения, является переход от грубой к более детальной безопасности.
Детализированная авторизация решает эту проблему, основывая доступ на таких атрибутах, как роли пользователя, действия и даже контекст, например время или местоположение, и такой детальный контроль доступа жизненно важен для современных приложений. В этой статье обсуждается, как
Благодаря таким функциям ZITADEL, как роли, метаданные и действия, пользователи могут получить очень подробный контроль доступа, подходящий для настройки нулевого доверия. Кроме того, ZITADEL может работать с внешними сервисами авторизации.
ЗИТАДЕЛЬ – это
Его основные цели включают предоставление готовых функций для аутентификации, авторизации, входа в систему и единого входа (SSO), а также возможность настройки через пользовательские интерфейсы.
Он поставляется с обширным журналом аудита для отслеживания всех изменений, позволяет разработчикам расширять функциональные возможности с помощью специального кода (действий), поддерживает широко признанные стандарты, такие как OIDC, OAuth, SAML и LDAP, подчеркивает простоту эксплуатации и масштабируемость и предлагает комплексные API-интерфейсы для универсальная интеграция.
ZITADEL использует RBAC для управления разрешениями пользователей, где разрешения привязаны к ролям, и пользователям назначаются эти роли. Это упрощает управление доступом пользователей в зависимости от их организационных ролей. Дополнительная функция позволяет делегировать роли другим организациям, облегчая обмен разрешениями с внешними объектами.
Это особенно ценно для взаимосвязанных или иерархических организаций.
Хотя эти возможности обеспечивают надежный контроль доступа, их может быть недостаточно для сложных задач авторизации, поэтому важно изучить детальную авторизацию в ZITADEL.
ZITADEL расширяет традиционный RBAC, представляя его динамический
С помощью действий ZITADEL можно создавать сценарии пост-аутентификации для анализа определенных атрибутов пользователя и блокировки доступа при необходимости.
Действия также могут устанавливать пользовательские утверждения для повышения эффективности системы ABAC, позволяя использовать расширенные модели авторизации, которые ограничивают доступ на основе таких атрибутов, как местоположение, время или любой определяемый фактор.
ZITADEL позволяет администраторам или уполномоченным разработчикам добавлять пользовательские метаданные для пользователей и организаций, расширяя возможности детального контроля доступа.
Он поддерживает агрегированные претензии, собирая дополнительные данные из внешних систем, таких как CRM или HR-инструменты. ZITADEL также может управлять уникальными ресурсами, такими как заказы на доставку или устройства IoT, и определять доступ на основе таких атрибутов, как пользователь-подписчик, роли, претензии, IP-адрес и т. д.
Несмотря на обширные функции ZITADEL, могут возникнуть случаи, когда потребуется более индивидуальный или детальный подход.
В настоящее время наиболее эффективным способом реализации детальной авторизации в ZITADEL является использование пользовательской логики приложения для небольших проектов или для более масштабных проектов с использованием доступного стороннего инструмента, такого как warrant.dev , cerbos.dev и т. д.
Эти инструменты можно интегрировать с ZITADEL, что еще больше расширяет ваши возможности для детальной авторизации.
Допустим, в медиакомпании имеется гипотетическое приложение для отдела новостей, которое взаимодействует с серверным API. Журналисты используют его для написания, а редакторы редактируют и публикуют эти статьи. Этот API, написанный в этом примере на Python Flask, имеет определенные конечные точки, и доступ к этим конечным точкам зависит от роли пользователя и его опыта. Конечные точки:
write_article
: Писать могут только журналисты.
edit_article
: только для редактирования статей редакторами.
review_articles
: для старших журналистов, а также редакторов среднего и старшего уровня для рецензирования статей.
publish_article
: для публикации журналистами среднего и старшего уровня, а также старшими редакторами. Внутри API используется JWT, выданный ZITADEL, для проверки того, кто делает запросы. Пользователям необходимо отправить действительный JWT в заголовке своего запроса. Этот JWT был получен, когда пользователь вошел в систему.
JWT содержит информацию о пользователе, например его роль и опыт. Эта информация, содержащаяся в пользовательских утверждениях, является ключевой для этого варианта использования. На основе этой информации серверная часть решает, может ли пользователь получить доступ к запрошенному ресурсу.
journalist
или editor
. Это ключевой момент, поскольку он определяет, кто и какой доступ получит в нашей настройке. Управление опытом/старшинством. Помимо ролей, отслеживается опыт пользователя (например, junior
, intermediate
и senior
в нашем примере). Если опыт пользователя меняется, ZITADEL обновляет его как метаданные. Если при подключении пользователя к ZITADEL не указан уровень опыта, система просто предполагает, что он «младший».
Разделение задач . При разработке этого API особое внимание было уделено обеспечению четкого разделения бизнес-логики и правил контроля доступа. Это имеет решающее значение для удобства сопровождения и масштабируемости приложения. Разделив бизнес-логику и правила доступа, мы получаем более понятную модульную структуру.
Это позволяет нам обновлять бизнес-действия и правила доступа, не затрагивая друг друга. Это повышает удобство сопровождения кода и упрощает управление по мере масштабирования приложения.
Кроме того, такая конструкция делает систему более безопасной, поскольку правила доступа абстрагируются от основной бизнес-логики, что снижает риск случайного появления уязвимостей безопасности при изменении бизнес-логики.
Создайте организацию Media House, перейдите в раздел «Проекты» и создайте новый проект под названием «Newsroom».
В проекте Newsroom нажмите кнопку «Создать» , чтобы создать новое приложение.
Перейдите на вкладку «Пользователи» в вашей организации, как показано ниже, и перейдите на вкладку «Пользователи службы ». В этой демонстрации мы будем создавать пользователей сервиса. Чтобы добавить пользователя сервиса, нажмите кнопку «Новый» .
Затем добавьте сведения о пользователе службы, выберите JWT в качестве типа токена доступа и нажмите «Создать» .
Нажмите кнопку «Действия» в правом верхнем углу. В раскрывающемся меню выберите «Создать секрет клиента» .
Скопируйте свой идентификатор клиента и секрет клиента. Нажмите Закрыть .
Теперь у вас есть пользователь службы и его учетные данные клиента.
Зайдите в «Авторизации» . Нажмите Создать.
Выберите пользователя и проект, для которого необходимо создать авторизацию. Нажмите Продолжить .
Здесь вы можете выбрать роль. Выберите роль журналиста для текущего пользователя. Нажмите Сохранить .
Вы можете видеть, что пользователь сервиса Лоис Лейн теперь имеет роль журналиста в проекте Newsroom .
Теперь давайте добавим метаданные в профиль пользователя, чтобы указать его уровень старшинства. Используйте «experience_level» в качестве ключа, а в качестве его значения выберите «младший», «средний» или «старший».
Хотя обычно мы можем предположить, что эти метаданные устанавливаются посредством вызова API, выполняемого приложением HR, для простоты и удобства понимания мы будем устанавливать метаданные непосредственно в консоли.
Перейдите в Метаданные . Нажмите «Изменить» .
Укажите experience_level в качестве ключа и Senior в качестве значения. Щелкните значок сохранения и нажмите кнопку «Закрыть» .
Теперь у пользователя есть необходимые метаданные, связанные с его учетной записью.
Вы также можете добавить еще несколько пользователей службы с разными ролями и уровнями опыта (с использованием метаданных), чтобы протестировать демо-версию, выполнив предыдущие шаги.
1. Нажмите Действия . Нажмите «Создать» , чтобы создать новое действие.
2. В разделе «Создать действие» присвойте действию то же имя, что и имя функции, т. е. AssignRoleAndExperienceClaims. В поле сценария скопируйте и вставьте следующий код и нажмите «Добавить» .
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); } }); }); }
Теперь, когда пользователь запрашивает токен доступа, действие будет выполнено, преобразовав роли пользователя и метаданные в необходимый формат и добавив их в качестве пользовательского утверждения к токену. Это пользовательское утверждение затем может использоваться сторонними приложениями для детального управления доступом пользователей.
Клонируйте проект с GitHub:
Запустите команду ниже, чтобы клонировать проект из этого репозитория GitHub:
git clone https://github.com/zitadel/example-fine-grained-authorization.git
Перейдите в каталог проекта:
После клонирования перейдите в каталог проекта с помощью
cd example-fine-grained-authorization
.
Настройте среду Python:
Убедитесь, что у вас установлены Python 3 и pip. Вы можете проверить это, запустив
python3 --version
иpip3 --version
в вашем терминале. Если у вас не установлен Python или pip, вам необходимо их установить.
Затем создайте новую виртуальную среду для этого проекта, запустив
python3 -m venv env
.
Активируйте среду, выполнив:
.\env\Scripts\activate
source env/bin/activate
После запуска этой команды ваш терминал должен указать, что теперь вы работаете внутри виртуальной среды env.
Установите зависимости:
Используя терминал в каталоге проекта (тот, который содержит файл require.txt), запустите
pip3 install -r requirements.txt
для установки необходимых зависимостей.
Настройте переменные среды:
Для проекта требуются определенные переменные среды. Заполните файл .env
значениями, полученными из 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>"
Запустите приложение:
API Flask (в app.py
) использует токены JWT и пользовательские утверждения для детального контроля доступа. Он проверяет пользовательское утверждение experience_level для ролей journalist
и editor
при каждом запросе, используя эту информацию, чтобы решить, может ли аутентифицированный пользователь получить доступ к запрошенной конечной точке.
app.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)
автор.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 (пример кода, имитирующего механизм правил)
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
Запустите приложение Flask, выполнив:
python3 app.py
Если все настроено правильно, ваше приложение Flask теперь должно работать.
Этот проект был разработан и протестирован с использованием Python 3, поэтому убедитесь, что вы используете интерпретатор Python 3.
Убедитесь, что вы клонировали репозиторий и установили необходимые зависимости, как описано ранее.
Запустите сценарий client_credentials_token_generator.py
, чтобы сгенерировать токен доступа.
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}")
Откройте терминал и перейдите в каталог проекта, затем запустите скрипт, используя python3:
python3 client_credentials_token_generator.py
В случае успеха на вашем терминале будет распечатан токен доступа. Это токен, который вы будете использовать для аутентификации ваших запросов к API.
Если вы не запускали Flask API ранее, запустите API, открыв другой терминал в каталоге проекта и выполнив:
python3 app.py
Сервер API должен быть запущен и готов принимать запросы.
Теперь вы можете использовать cURL или любой другой HTTP-клиент (например, Postman) для отправки запросов к API. Не забудьте заменить your_access_token
в командах Curl на токен доступа, полученный на шаге 2.
Сценарий 1: Младший редактор пытается отредактировать статью (успех)
Пользователь с ролью editor
и junior
уровнем опыта пытается вызвать конечную точку edit_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/edit_article
Ожидаемый результат: {"message": "Article edited successfully"}
Сценарий 2: Младший редактор пытается опубликовать статью (неудача)
Пользователь с ролью editor
и junior
уровнем опыта пытается вызвать конечную точку publish_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/publish_article
Ожидаемый результат: {"message": "Access denied! You are a junior editor and therefore cannot access publish_article"}
Сценарий 3: Старший журналист пытается написать статью (успех)
Пользователь с ролью journalist
и senior
уровнем опыта пытается вызвать конечную точку write_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/write_article
Ожидаемый результат: {"message": "Article written successfully"}
Сценарий 4: Младший журналист пытается рецензировать статьи (неудача)
Пользователь с ролью journalist
и младшим уровнем опыта пытается вызвать конечную точку review_articles
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/review_articles
Ожидаемый результат: {"message": "Access denied! You are a junior journalist and therefore cannot access review_articles"}
Сценарий 5: Старший редактор пытается просмотреть статьи (успех)
Пользователь с ролью editor
и senior
уровнем опыта пытается получить доступ к конечной точке review_articles
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/review_articles
Ожидаемый результат: {"message": "Article reviewed successfully"}
Сценарий 6: Журналист среднего уровня пытается опубликовать статью (успех)
Пользователь с ролью journalist
и intermediate
уровнем опыта пытается получить доступ к конечной точке publish_article
.
curl -H "Authorization: Bearer <your_access_token>" -X POST http://localhost:5000/publish_article
{"message": "Article published successfully"}
В этой статье мы рассмотрели важность перехода от традиционного RBAC к более детальному и детализированному подходу авторизации с использованием ZITADEL.
Мы углубились в его функции, такие как динамические действия для ABAC, возможность интеграции со сторонними инструментами, и увидели, как эти возможности можно практически применить в реальном сценарии.
По мере роста требований кибербезопасности такие платформы, как ZITADEL, предоставляют необходимые решения для сложных задач авторизации.