1. Предисловие
В первых двух статьях мы разобрали основы спецификации STAC (SpatioTemporal Asset Catalog), её объектную модель и философию, превращающую разрозненные архивы геоданных в единую, машиночитаемую «библиотеку». Мы увидели, как STAC описывает каталоги (catalog), коллекции (collection), элементы (item) и их ресурсы (assets), создавая универсальный язык для работы с геопространственной информацией.
В комментариях были затронуты две темы, которые просили рассмотреть в новых статьях — семантическая паутина и универсальные браузеры, которые требуют постепенного перехода от теории к практике. Действительно, какая польза от идеально структурированного каталога, если с ним неудобно или невозможно работать? Поэтому, прежде чем углубиться в онтологии, мы рассмотрим инструменты взаимодействия с STAC.
Эта статья посвящена клиентской стороне экосистемы — STAC-браузерам, а также ключевому аспекту их работы в корпоративной среде — безопасному доступу к данным через STAC-API. Мы разберём, как устроен универсальный браузер, и представим частную реализацию стека STAC-сервера с распределённой системой управления доступом IAM (Identity and Access Management), где каждый запрос, от просмотра метаданных до скачивания тайла, проходит через цепочку авторизации.
Оглавление
2. STAC-браузер как окно в каталог
По своей сути, STAC-браузер — это веб-приложение, которое визуализирует содержимое STAC-каталога. Его основная задача — предоставить интуитивно понятный интерфейс для поиска и обнаружения данных: по карте, по дате, по свойствам (properties). Он не хранит сами данные, а является «умным клиентом», который взаимодействует с каталогом через его корневой catalog.json или, что чаще, через STAC-API.
Принцип универсальности любого STAC-браузера строится на двух китах:
Жёсткое следование базовой спецификации. Браузер опирается на гарантированно присутствующие в любом валидном каталоге поля:
id,geometry,datetime,bbox, ссылки наassets. Это его «несгораемый минимум» для отображения.Всеядность по отношению к расширениям. Если каталог использует расширения (например,
eo,sar,proj), браузер не должен ломаться. Вместо этого он должен уметь показывать дополнительные поля изpropertiesв виде читаемой таблицы. Универсальный браузер не «зашивает» в себя логику обработки конкретных расширений, а динамически отображает все найденные метаданные.
Типичный рабочий процесс браузера (например, stac-browser от Radiant Earth):
пользователь вводит URL корня каталога или API;
браузер запрашивает корневой каталог (
/), получает список дочерних коллекций (/collections);при выборе коллекции загружаются её описание и, часто через поисковый запрос к API (
/search), первые элементы (items);элементы отображаются, в том числе на временной шкале и карте (чаще используются OpenLayers);
Клик по элементу открывает детальную панель со всеми его свойствами (
properties) и спискомassets(GeoTIFF, JSON, PNG и т.д.), которые можно просмотреть или скачать.
Такой подход позволяет одним и тем же браузером исследовать каталог спутниковых снимков Landsat, архив погодных радарных данных и коллекцию 3D-моделей города — при условии, что все они соответствуют базовой спецификации STAC.
И тут важно отметить, что большинство STAC-браузеров реализует всю свою логику преимущественно через STAC-API. Т.е. все, что вы делаете в браузере может быть воспроизведено на уровне машинного доступа через API STAC-каталога, при разработке и реализации автоматов поиска и обработки данных или производства высокоуровневых информационно-аналитических продуктов из большего количества разнородных входных данных. О таком перспективном направлении как "мат��матика на метаданных" мы поговорим отдельно чуть позже.
3. Потребность в контроле доступа
Публичные каталоги, подобные Earth Search, прекрасно работают с классическими открытыми браузерами. Но в корпоративной или государственной среде данные редко бывают полностью публичными. Возникают вопросы:
Как скрыть определённые коллекции или элементы от одних пользователей и показать другим?
Как ограничить скачивание исходных данных (
assets) только авторизованным группам?Как интегрировать STAC-каталог в единую экосистему безопасности организации?
Стандарт STAC сознательно не включает механизмы аутентификации и авторизации, оставляя это на усмотрение реализаций конкретных экосистем. Это и есть точка, где заканчивается универсальность публичного браузера и начинается необходимость в кастомизированном решении.
4. Реализация Identity and Access Management (IAM) для STAC
Далее мы рассмотрим кастомный STAC-браузер, расширенный подсистемой безопасного доступа. Его архитектура построена вокруг модели Policy-Based Access Control (PBAC) и состоит из независимых сервисов, соответствующих стандартам XACML:
Сервис управления данными (СУД / Policy Administration Point, PAP): Хранит политики доступа в виде правил («Роль R имеет право Чтения на Ресурс типа Коллекция с атрибутом
category=secret»). Политики пишутся в декларативном формате (например, на Rego) и централизовано управляются администраторами.Сервис каталога STAC (STAC API / Policy Enforcement Point, PEP): Это наш основной сервис, предоставляющий STAC-API. Его ключевая роль — перехват всех входящих запросов. При получении запроса PEP не обрабатывает его сразу, а формирует запрос на авторизацию, включающий в себя: идентификатор пользователя (из токена), действие (
read,download), целевой ресурс (например, путь/collections/secret-sat/itemsили конкретныйasset).Сервис авторизации (Policy Decision Point, PDP): Принимает запрос от PEP, загружает соответствующие политики из СУД, оценивает их и выносит вердикт:
PermitилиDeny. PDP не знает о логике приложения, он работает только с атрибутами, что делает систему гибкой.Keycloak (Identity Provider / Policy Information Point, PIP): Выступает в роли SSO. Он аутентифицирует пользователей, выдает JWT-токены с ролями и атрибутами (
groups,department). PEP и PDP используют эти утверждения (claims) из токена для принятия решений.
Как это работает в реальном сценарии:
Пользователь логинится в браузере через Keycloak, получая токен.
Браузер запрашивает у STAC-API список коллекций, подставляя токен в заголовок
Authorization: Bearer <JWT>.PEP в STAC-API перехватывает запрос к
/collections. Он извлекает из JWT идентификатор пользователя и его атрибуты, определяет, что запрашивается операцияreadнад ресурсомcollection_list. PEP отправляет эти данные в PDP.PDP проверяет политики и понимает, что пользователь из группы
analystsне должен видеть коллекцию с тегомcategory=top_secret. PDP возвращает решениеPermitс дополнительным атрибутом-фильтром:"visible_collection_ids": ["landsat", "open-data"].PEP, получив
Permit, передаёт управление основной логике STAC-API. Та, в свою очередь, выполняет запрос к базе данных, добавив фильтр по ID коллекций из решения PDP. В результате пользователь видит в браузере только разрешённые ему коллекции. Для него система выглядит так, будто других коллекций просто не существует.
5. Детали под микроскопом
Всё начинается с того, что PBAC (Policy-Based Access Control), упомянутый ранее — это не модель, а скорее архитектурный подход или парадигма. Его суть в том, решения о доступе принимаются не на основе жёстко зашитых правил в коде, а на основе внешних, декларативных политик. Политики описывают, кому, при каких условиях и что разрешено. Где хранятся эти политики и как они выглядят — дело реализации.
ABAC (Attribute-Based Access Control) и RBAC (Role-Based Access Control) — это уже конкретные модели, определяющие логику этих политик.
RBAC оперирует ролями: «пользователь с ролью «аналитик» может читать коллекции».
ABAC оперирует атрибутами всех сущностей: «пользователь, чей
clearance_level >= 3иdepartment = 'remote-sensing', может читать коллекции с тегомclassification = 'restricted'».
В описываемой архитектуре мы используем PBAC как фундамент. У нас есть сервис PDP, который оценивает политики, и сервис PAP, где эти политики хранятся. А вот содержимое этих политик может быть как чисто ролевым (RBAC), так и атрибутивным (ABAC). Более того, мы активно комбинируем оба подхода в рамках одной политики. Например:
«Разрешить доступ, если пользователь состоит в группе
analysts(роль) И уровень его допуска (clearance_level) не ниже 3 (атрибут)».
Это типичный пример гибридной модели, которую часто называют PBAC (в широком смысле) или просто ABAC с поддержкой ролей. Сами роли в ABAC могут быть просто одним из атрибутов пользователя (как у нас в JWT есть поле groups).
Ключевым элементом нашей модели IAM является контроль доступа на основе атрибутов (ABAC), который идёт дальше классического разграничения по ролям. Суть ABAC заключается в том, что решение о доступе принимается динамически на основе комбинации атрибутов субъекта (кто запрашивает), объекта (что запрашивают), действия (чтение, запись, скачивание) и окружения (время суток, уровень доверия к сети). Для STAC такой подход оказывается едва ли не «родным». Почему? Потому что объектом доступа здесь выступают не абстрактные файлы, а богатые метаданными сущности — коллекции, элементы и их свойства.
Политика может выглядеть так: «Разрешить доступ к элементу, если его datetime попадает в интервал, указанный в атрибутах пользователя, И значение eo:cloud_cover меньше 20%». Это позволяет строить гибкие, контекстно-зависимые правила без необходимости переписывать код API. В конечной реализации можно комбинировать ABAC с классическим ролевым доступом RBAC: роли (analyst, guest, admin) задают широкие контексты, а атрибуты уточняют разрешения внутри них. Такой гибрид даёт администратору возможность не плодить сотни ролей под каждый проект, а описать доступ декларативно, через понятные метаданные STAC.
Таким образом, мы строим систему на политиках (PBAC), а внутри политик используем и роли, и атрибуты, чтобы получить максимальную гибкость. Это позволяет нам не плодить сотни ролей под каждый проект, а описывать доступ декларативно, через понятные метаданные STAC, сохраняя при этом удобство ролевой модели для широких категорий пользователей.
Давайте рассмотрим, как токен из заголовка проходит через эту цепочку на уровне конкретных HTTP-вызовов. Возьмем стандартные эндпоинты STAC-API.
Что хранится в токене?
Токен доступа, выдаваемый Keycloak, представляет собой JWT (JSON Web Token). Помимо стандартных полей (sub — идентификатор пользователя, iss — издатель, exp — срок действия), мы расширяем его кастомными claims, которые необходимы для принятия решений об авторизации. Пример декодированного токена:
json
{ "sub": "a1b2c3d4-e5f6-7890-1234-567890abcdef", "email": "ivanov@corporation.ru", "preferred_username": "ivanov", "groups": ["analysts", "landsat-users"], "department": "remote-sensing", "clearance_level": 3 }
Здесь groups — группы пользователя в Keycloak (могут соответствовать ролям), department и clearance_level — пользовательские атрибуты, добавленные через протокол сопоставления (mappers) в Keycloak. Именно эти данные становятся основой для формирования запроса к PDP.
Keycloak выступает в роли PIP (Policy Information Point) — источника атрибутов. В идеальном случае все нужные атрибуты уже лежат в токене, и PEP не делает дополнительных вызовов. Однако при необходимости PEP может обратиться к UserInfo endpoint Keycloak, чтобы получить расширенную информацию о пользователе, не умещающуюся в токен (например, список проектов, к которым пользователь привязан). Обычно стараются минимизировать такие запросы ради производительности и чаще всего размещают ключевые атрибуты в самом JWT.
Обработка запроса в PEP на Python (FastAPI)
Ниже приведён упрощённый пример middleware для FastAPI, который перехватывает запросы к защищённым эндпоинтам STAC-API, извлекает токен, проверяет его и отправляет запрос на авторизацию в сервис PDP.
python
Code
import jwt from fastapi import Request, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import httpx import json # Конфигурация JWKS_URL = "https://keycloak.corporation.ru/auth/realms/stac-realm/protocol/openid-connect/certs" PDP_URL = "http://pdp-service:8081/decision" ALGORITHMS = ["RS256"] security = HTTPBearer() async def verify_token(credentials: HTTPAuthorizationCredentials) -> dict: token = credentials.credentials # В реальности здесь должна быть загрузка и кэширование JWKS, # проверка подписи, срока и т.д. Опустим детали для краткости. # Используем библиотеку python-jose или PyJWT с JWKS. # Допустим, мы получили payload: payload = jwt.decode(token, options={"verify_signature": False}) # только для демо! # В бою нужно проверять подпись! return payload async def authorize(subject: dict, action: str, resource: dict) -> dict: """ Отправляет запрос в PDP и возвращает решение с возможными фильтрами. """ async with httpx.AsyncClient() as client: response = await client.post( PDP_URL, json={ "subject": subject, "action": action, "resource": resource, "context": {"source_ip": "..."} # можно добавить атрибуты окружения } ) response.raise_for_status() return response.json() @app.middleware("http") async def authorization_middleware(request: Request, call_next): # Пропускаем публичные эндпоинты (например, /docs, /.well-known/health) if request.url.path in ["/docs", "/openapi.json", "/health"]: return await call_next(request) # Извлечение токена из заголовка Authorization auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): raise HTTPException(status_code=401, detail="Missing or invalid token") token = auth_header.split(" ")[1] try: payload = await verify_token(token) # проверка подписи опущена except Exception as e: raise HTTPException(status_code=401, detail="Invalid token") # Формируем субъект с атрибутами из токена subject = { "id": payload.get("sub"), "groups": payload.get("groups", []), "department": payload.get("department"), "clearance_level": payload.get("clearance_level") } # Определяем действие и ресурс на основе запроса # Здесь логика парсинга пути - для примера упростим if request.method == "GET" and request.url.path.startswith("/collections/"): # Например: /collections/landsat/items path_parts = request.url.path.strip("/").split("/") if len(path_parts) >= 2 and path_parts[1] == "items": collection_id = path_parts[0] # landsat action = "list_items" resource = {"type": "collection_items", "collection_id": collection_id} else: # Запрос к конкретной коллекции или списку коллекций # ... action = "read" resource = {"type": "collection", "collection_id": path_parts[1]} if len(path_parts) > 1 else {"type": "collections_list"} else: # Другие эндпоинты action = request.method.lower() resource = {"type": "unknown"} # Запрос в PDP decision = await authorize(subject, action, resource) if decision["effect"] != "Permit": raise HTTPException(status_code=403, detail="Access denied") # Если PDP вернула фильтры (например, список разрешённых коллекций), # их можно добавить в request.state для использования в роутах request.state.auth_filters = decision.get("filters", {}) response = await call_next(request) return response
Что происходит в PDP?
Сервис PDP может быть реализован на любом стеке. Для простоты представим его на Python с использованием простого движка правил (хотя в продакшене чаще используют OPA с языком Rego). Пример функции, принимающей запрос и возвращающей решение:
python
Code
from fastapi import FastAPI from pydantic import BaseModel from typing import List, Optional app = FastAPI() class Subject(BaseModel): id: str groups: List[str] = [] department: Optional[str] = None clearance_level: Optional[int] = None class Resource(BaseModel): type: str collection_id: Optional[str] = None # другие атрибуты class DecisionRequest(BaseModel): subject: Subject action: str resource: Resource context: dict = {} @app.post("/decision") def decide(request: DecisionRequest): subject = request.subject action = request.action resource = request.resource # Пример политики ABAC: разрешить доступ к коллекции только если clearance_level >= 3 if resource.type == "collection_items" and resource.collection_id == "secret-sat": if subject.clearance_level and subject.clearance_level >= 3: return {"effect": "Permit", "filters": {}} else: return {"effect": "Deny"} # Политика: analysts могут видеть список коллекций, но не все коллекции if resource.type == "collections_list" and action == "read": if "analysts" in subject.groups: # Возвращаем фильтр: видеть только коллекции landsat и open-data return { "effect": "Permit", "filters": {"allowed_collections": ["landsat", "open-data"]} } else: return {"effect": "Deny"} # Базовая политика: все остальное разрешено (для демо) return {"effect": "Permit", "filters": {}}
Здесь PDP использует атрибуты субъекта (clearance_level, groups) и ресурса (collection_id) для принятия решения. Заметьте, что PDP может также запрашивать дополнительные атрибуты из PIP — например, уровень секретности самой коллекции из метаданных (хранящихся в СУД), если они не были переданы в запросе. Для этого PDP может обращаться к базе данных коллекций или к сервису метаданных.
Базовые эндпоинты и вызовы:
GET /(корень API): Возвращает описание API (/conformance,/collectionsи т.д.). Доступ обычно публичный, но PEP может проверить валидность токена.bash
# Запрос с токеном curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ https://stac-api.corporation.ru/GET /collections: Список коллекций. Здесь PEP активно взаимодействует с PDP.bash
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ https://stac-api.corporation.ru/collectionsВнутри PEP: Формируется запрос к PDP:
{ "subject": {"id": "user123", "groups": ["analysts"]}, "action": "list", "resource": {"type": "collection"}}. Ответ PDP с фильтром интегрируется в SQL-запрос:SELECT * FROM collections WHERE id IN ('landsat', 'open-data').GET /collections/{collectionId}/items: Поиск элементов в коллекции.bash
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \ "https://stac-api.corporation.ru/collections/secret-sat/items?limit=10"Внутри PEP: PDP проверяет, может ли
user123читать коллекциюsecret-sat. Если решениеDeny, API немедленно возвращает 403 Forbidden. ЕслиPermit, запрос выполняется, и PDP может дополнительно вернуть фильтр по полямdatetimeилиbbox(например, "можно видеть только данные старше 2 лет").Доступ к
assets— ключевой момент: Прямые ссылки на данные вassets(например,"href": "s3://bucket/secret/image.tif") также защищены. Браузер не получает прямую ссылку на S3. Вместо этого вasset.hrefподставляется URL прокси-сервиса "s3" (который также является PEP) внутри STAC-API:json
{ "type": "image/tiff", "href": "https://stac-api.corporation.ru/collections/secret-sat/items/item-001/assets/data/proxy", "title": "Сами данные" }Когда браузер пытается скачать этот
asset, запрос идёт через наш API, где PEP снова запрашивает у PDP решение для действияdownloadна ресурсasset. Только после разрешения файл потоково передаётся пользователю.
6. Кэширование - скорость vs безопасность
Очевидно, что постоянные запросы к PDP на каждый вызов API создают задержки. Для ускорения работы необходимо внедрить кэширование решений PDP на уровне PEP.
Механизм работы
При первом запросе пользователя
user123к/collections/secret-sat/itemsPEP формирует уникальный ключ кэша, например, хэш от конкатенации:user123 + collectionId + action + "list_items".PEP проверяет локальный (in-memory) или распределённый (Redis) кэш по этому ключу. Если решение отсутствует, запрос идёт к PDP.
Полученное решение
Permit(с фильтрами) кэшируется на заданное время жизни (TTL), например, 60 секунд.Все последующие запросы этого пользователя к элементам той же коллекции в течение 60 секунд будут обслуживаться из кэша, без обращения к PDP.
Важные нюансы кэширования
Инвалидация
При изменении политик в СУД администратор отправляет широковещательный сигнал всем PEP об очистке кэша.
Контекст
Кэшируется не просто Permit/Deny, а целое решение с атрибутами (фильтрами по дате, географии). Это критически важно для корректной работы.
Запросы на запись (POST, PUT)
Для них TTL кэша устанавливается в ноль или близко к нулю, чтобы каждое изменение проходило свежую авторизацию.
Такая схема позволяет снизить нагрузку на PDP и задержку для пользователя до минимума, сохраняя при этом высокий уровень безопасности. Для конечного аналитика работа в браузере остаётся быстрой и отзывчивой, в то время как каждый его клик безопасно контролируется на уровне инфраструктуры.
Кэширование решений в PEP
Чтобы избежать повторных вызовов PDP для одинаковых запросов одного пользователя, PEP использует кэш. Ключ формируется как хэш от комбинации идентификатора пользователя, действия и уникального идентификатора ресурса (или его типа). В нашем примере на FastAPI можно использовать @lru_cache или Redis:
python
from functools import lru_cache @lru_cache(maxsize=1024) def get_cached_decision(cache_key: str) -> dict: # В реальности здесь обращение к Redis pass # При обработке запроса: cache_key = f"{subject['id']}:{action}:{resource.get('type')}:{resource.get('collection_id','')}" decision = get_cached_decision(cache_key) if not decision: decision = await authorize(subject, action, resource) # сохраняем в кэш с TTL 60 секунд
Кэш инвалидируется при изменении политик (PEP получает сигнал от СУД) или по истечении TTL.
Таким образом, благодаря интеграции с Keycloak и распределённой архитектуре PDP/PEP мы получаем систему, где каждый HTTP-вызов к STAC-API автоматически проверяется на соответствие политикам, а все решения кэшируются для высокой производительности.
7. Заключение
STAC — это не просто формат описания данных. Это основа для построения целых экосистем взаимодействия с пространствено-временной информацией. Универсальные браузеры демонстрируют силу стандартизации, позволяя исследовать любые каталоги через единый интерфейс.
Однако настоящая сила стандарта раскрывается, когда он ��тановится не целью, а фундаментом. Реализация STAC-API с интегрированной системой управления доступом на базе PDP/PEP показывает, как открытые спецификации могут быть адаптированы для строгих корпоративных требований. Мы получаем гибкость декларативных политик, независимость сервисов авторизации и прозрачное для пользователя кэширование.
В следующей, четвёртой части цикла, мы наконец обратимся к теме, интересовавшей многих читателей с самого начала — семантической паутине (Linked Data) и онтологиям. Мы разберём, как расширение STAC sat (Scientific Annotation Tool) позволяет превращать сухие JSON-метаданные в семантическую сеть взаимосвязанных понятий, как аннотировать данные связями с внешними онтологиями (например, OGC Definitions Server) и какие новые возможности поиска и анализа это открывает в части более широкой вселенной — машиночитаемого знания.
P.S.
Уважаемые читатели!
Мы ищем текущих владельцев русского и английского сайтов проекта Маяк, для реанимации и поддержки ресурсов. Нужна ваша помощь!
Требуется помощь!
В 2017 году мы с командой убежденных единомышленников запустили с космодрома Байконур первый и до сих пор единственный в России полностью частный космический аппарат формата cubesat - "Маяк", разработанный исключительно на средства, собранные путем нескольких краудфандинговых компаний. Целью проекта была - популяризация космонавтики и космических исследований в России, а также повышение привлекательности научно-технического образования в среде молодежи.
Проект состоялся, Маяк был запущен, и спустя четыре года уже даже сошел с орбиты.
К сожалению, за прошедшие годы судьба развела членов команды Маяка по разным другим проектам, и поддержка двух основных интернет-ресурсов cosmomayak.com и cosmomayak.ru была заброшена...
В 2025 г., после торжественной передачи второго рабочего экземпляра спутника Маяк в Музей космонавтики мы решили вновь объединить усилия и реанимировать наши два сайта для истории. А может быть и для возобновлении работы)
К сожалению, пока не удается найти контакты текущих владельцев доменов (а их кто-то регулярно оплачивает). Предполагаем, это кто-то из разработчиков, из старой команды Алекандра Панова, нашего PR-директора. Англоязычный сайт еще частично работает, а вот русскоязычный закрылся парковочной страницей какого-то интернет-магазина.
Будем признательны любой информации и контактам, чтобы выйти на текущих владельцев доменов, с целью выкупа и реанимации ресурсов и контента. Также очень интересуют действующие архивы русского и английского сайтов Маяка, если таковые у кого-то из старых веб-мастеров остались.
Заранее спасибо, друзья!
