Skip to content

Commit cb10439

Browse files
committed
Second draft
1 parent 665245d commit cb10439

File tree

1 file changed

+62
-108
lines changed

1 file changed

+62
-108
lines changed

README.md

Lines changed: 62 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,27 @@ STAC Auth Proxy is a proxy API that mediates between the client and an internall
1111
- 🎟️ Content Filtering: Apply CQL2 filters to client requests, filtering API content based on user context
1212
- 📖 OpenAPI Augmentation: Update [OpenAPI](https://swagger.io/specification/) with security requirements, keeping auto-generated docs (e.g. [Swagger UI](https://swagger.io/tools/swagger-ui/)) accurate
1313

14+
## Installation
15+
16+
> [!NOTE]
17+
> Currently, the project is only installable by downlaoding the repository. It will eventually be available on Docker (#5) and PyPi (#30).
18+
19+
This project uses [`uv`](https://docs.astral.sh/uv/) to manage project dependencies and environment.
20+
21+
```sh
22+
uv sync
23+
```
24+
25+
## Running
26+
27+
```sh
28+
uv run python -m stac_auth_proxy
29+
```
30+
1431
## Configuration
1532

33+
The application is configurable via environment variables.
34+
1635
### Core Settings
1736

1837
- `DEBUG`
@@ -23,22 +42,28 @@ STAC Auth Proxy is a proxy API that mediates between the client and an internall
2342
- **Example:** `true`
2443

2544
- `UPSTREAM_URL`
45+
2646
- The STAC API to proxy requests to
2747
- **Type:** HTTP(S) URL
2848
- **Required:** Yes
2949
- **Example:** `https://your-stac-api.com/stac`
3050

31-
### Authentication
32-
3351
- `OIDC_DISCOVERY_URL`
52+
3453
- OpenID Connect discovery document URL
3554
- **Type:** HTTP(S) URL
3655
- **Required:** Yes
3756
- **Example:** `https://auth.example.com/.well-known/openid-configuration`
3857

58+
- `OIDC_DISCOVERY_INTERNAL_URL`
59+
- The internal network OpenID Connect discovery document URL
60+
- **Type:** HTTP(S) URL
61+
- **Required:** No, defaults to value of `OIDC_DISCOVERY_URL`
62+
- **Example:** `http://auth/.well-known/openid-configuration`
63+
3964
### Access Control
4065

41-
Routes can be configured as requiring a valid authentication token by by specifying a blanket `default_public` rule and then explicit overrides (`private_endpoints` or `public_endpoints`) when exceptions are necessary.
66+
Routes can be configured as requiring a valid authentication token by by specifying a blanket `default_public` rule and then explicit overrides (`private_endpoints` or `public_endpoints`).
4267

4368
- `DEFAULT_PUBLIC`
4469

@@ -49,8 +74,8 @@ Routes can be configured as requiring a valid authentication token by by specify
4974

5075
- `PRIVATE_ENDPOINTS`
5176

52-
- **Description:** Endpoints explicitely marked as requiring authentication
53-
- **Type:** JSON object mapping regex patterns to HTTP methods
77+
- **Description:** Endpoints explicitely marked as requiring authentication, for use when `DEFAULT_PUBLIC == True`
78+
- **Type:** JSON object mapping regex patterns to HTTP methods OR to tuples of HTTP methods and an array of strings representing required scopes.
5479
- **Default:**
5580
```json
5681
{
@@ -63,7 +88,7 @@ Routes can be configured as requiring a valid authentication token by by specify
6388
```
6489

6590
- `PUBLIC_ENDPOINTS`
66-
- **Description:** Endpoints explicitely marked as not requiring authentication
91+
- **Description:** Endpoints explicitely marked as not requiring authentication, for use when `DEFAULT_PUBLIC == False`
6792
- **Type:** JSON object mapping regex patterns to HTTP methods
6893
- **Default:**
6994
```json
@@ -116,14 +141,6 @@ Routes can be configured as requiring a valid authentication token by by specify
116141

117142
## Architecture
118143

119-
### Application Structure
120-
121-
```mermaid
122-
graph TD
123-
Client --> Proxy[STAC Auth Proxy]
124-
Proxy --> STAC[Internal STAC API]
125-
```
126-
127144
### Middleware Stack
128145

129146
The middleware stack is processed in reverse order (bottom to top):
@@ -136,17 +153,17 @@ The middleware stack is processed in reverse order (bottom to top):
136153

137154
2. **BuildCql2FilterMiddleware**
138155

139-
- Builds CQL2 filters based on user context
140-
- Different handling for GET vs POST/PUT/PATCH requests
156+
- Builds CQL2 filters based on request context
141157
- Stores filter in request state
142158

143159
3. **ApplyCql2FilterMiddleware**
144160

161+
- Retrieves filter from request state
145162
- Applies the built CQL2 filter to requests
146163
- Modifies query strings for GET requests
147164
- Modifies JSON bodies for POST/PUT/PATCH requests
148165

149-
4. **OpenApiMiddleware** (optional)
166+
4. **OpenApiMiddleware**
150167

151168
- Modifies OpenAPI specification
152169
- Adds security requirements
@@ -156,104 +173,41 @@ The middleware stack is processed in reverse order (bottom to top):
156173
- Adds processing time headers
157174
- Useful for monitoring/debugging
158175

159-
### Request Flow
176+
### Data filtering via CQL2
160177

161-
#### GET Request Flow
178+
In order to provide row-level content filtering, the system supports generating CQL2 filters based on request context. These CQL2 filters are then set on outgoing requests prior to the upstream API.
179+
180+
> [!IMPORTANT]
181+
> The upstream STAC API must support the [STAC API Filter Extension](https://github.com/stac-api-extensions/filter/blob/main/README.md).
182+
183+
> [!TIP]
184+
> Integration with external authorization systems (e.g. [Open Policy Agent](https://www.openpolicyagent.org/)) can be achieved by replacing the default `BuildCql2FilterMiddleware` with a custom async middleware that is capable of generating [`cql2.Expr` objects](https://developmentseed.org/cql2-rs/latest/python/#cql2.Expr).
185+
186+
#### Example GET Request Flow
162187

163188
```mermaid
164189
sequenceDiagram
165190
Client->>Proxy: GET /collections
166191
Note over Proxy: EnforceAuth checks credentials
167192
Note over Proxy: BuildCql2Filter creates filter immediately
168193
Note over Proxy: ApplyCql2Filter modifies query string
169-
Proxy->>STAC API: Modified GET request
170-
STAC API->>Client: Filtered response
194+
Proxy->>STAC API: GET /collection?filter=(collection=landsat)
195+
STAC API->>Client: Response
171196
```
172197

173-
#### POST Request Flow
174-
175-
```mermaid
176-
sequenceDiagram
177-
Client->>Proxy: POST /search
178-
Note over Proxy: EnforceAuth checks credentials
179-
Note over Proxy: BuildCql2Filter accumulates body
180-
Note over Proxy: BuildCql2Filter creates filter
181-
Note over Proxy: ApplyCql2Filter modifies body
182-
Proxy->>STAC API: Modified POST request
183-
STAC API->>Client: Filtered response
184-
```
185-
186-
## Key Components
187-
188-
### CQL2 Filter System
189-
190-
The CQL2 filtering system is split into two middlewares for separation of concerns:
191-
192-
1. **Builder (`BuildCql2FilterMiddleware`)**
193-
194-
```python
195-
async def set_filter(body: Optional[dict] = None) -> None:
196-
cql2_filter = await filter_builder({
197-
"req": {
198-
"path": request.url.path,
199-
"method": request.method,
200-
"query_params": dict(request.query_params),
201-
"path_params": requests.extract_variables(request.url.path),
202-
"headers": dict(request.headers),
203-
"body": body,
204-
},
205-
**scope["state"],
206-
})
207-
```
208-
209-
- Creates filters based on request context
210-
- Handles both GET and POST requests differently
211-
- Validates filters before applying
212-
213-
2. **Applier (`ApplyCql2FilterMiddleware`)**
214-
- Modifies requests to include the built filter
215-
- GET: Modifies query string
216-
- POST/PUT/PATCH: Modifies JSON body
217-
218-
## Error Handling
219-
220-
- JSON parsing errors are caught and logged
221-
- Filter validation before application
222-
- Authentication errors handled by middleware
223-
- Debug endpoint for troubleshooting (when enabled)
224-
225-
This design provides a robust, secure, and efficient proxy layer for STAC APIs while maintaining flexibility for different deployment scenarios and requirements.
226-
227-
### CQL2 Filters
228-
229-
| Method | Endpoint | Action | Filter | Strategy |
230-
| -------- | ---------------------------------------------- | ------ | ------ | ---------------------------------------------------------------------------------------------------------- |
231-
| `POST` | `/search` | Read | Item | Append body with generated CQL2 query. |
232-
| `GET` | `/search` | Read | Item | Append query params with generated CQL2 query. |
233-
| `GET` | `/collections/{collection_id}/items` | Read | Item | Append query params with generated CQL2 query. |
234-
| `POST` | `/collections/{collection_id}/items` | Create | Item | Validate body with generated CQL2 query. |
235-
| `PUT` | `/collections/{collection_id}/items/{item_id}` | Update | Item | Fetch STAC Item and validate CQL2 query; merge STAC Item with body and validate with generated CQL2 query. |
236-
| `DELETE` | `/collections/{collection_id}/items/{item_id}` | Delete | Item | Fetch STAC Item and validate with CQL2 query. |
237-
238-
#### Recipes
239-
240-
Only return collections that are mentioned in a `collections` array encoded within the auth token.
241-
242-
```
243-
"A_CONTAINEDBY(id, ('{{ token.collections | join(\"', '\") }}' ))"
244-
```
245-
246-
## Installation
247-
248-
Set up connection to upstream STAC API and the OpenID Connect provider by setting the following environment variables:
249-
250-
```bash
251-
export UPSTREAM_URL="https://some.url"
252-
export OIDC_DISCOVERY_URL="https://your-openid-connect-provider.com/.well-known/openid-configuration"
253-
```
254-
255-
Install software:
256-
257-
```bash
258-
uv run python -m stac_auth_proxy
259-
```
198+
#### Filters
199+
200+
| Supported | Method | Endpoint | Action | Filter | Strategy |
201+
| --------- | -------- | ---------------------------------------------- | ------ | ---------- | ------------------------------------------------------------------------------------------------------ |
202+
|| `POST` | `/search` | Read | Item | Append body with generated CQL2 query. |
203+
|| `GET` | `/search` | Read | Item | Append query params with generated CQL2 query. |
204+
| ❌ (#22) | `POST` | `/collections/` | Create | Collection | Validate body with generated CQL2 query. |
205+
| ❌ (#23) | `GET` | `/collections/{collection_id}` | Read | Collection | Append query params with generated CQL2 query. |
206+
| ❌ (#22) | `PUT` | `/collections/{collection_id}}` | Update | Collection | Fetch Collection and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
207+
| ❌ (#22) | `DELETE` | `/collections/{collection_id}` | Delete | Collection | Fetch Collectiion and validate with CQL2 query. |
208+
|| `GET` | `/collections/{collection_id}/items` | Read | Item | Append query params with generated CQL2 query. |
209+
| ❌ (#25) | `GET` | `/collections/{collection_id}/items/{item_id}` | Read | Item | Validate response against CQL2 query. |
210+
| ❌ (#21) | `POST` | `/collections/{collection_id}/items` | Create | Item | Validate body with generated CQL2 query. |
211+
| ❌ (#21) | `PUT` | `/collections/{collection_id}/items/{item_id}` | Update | Item | Fetch Item and validate CQL2 query; merge Item with body and validate with generated CQL2 query. |
212+
| ❌ (#21) | `DELETE` | `/collections/{collection_id}/items/{item_id}` | Delete | Item | Fetch Item and validate with CQL2 query. |
213+
| ❌ (#21) | `POST` | `/collections/{collection_id}/bulk_items` | Create | Item | Validate items in body with generated CQL2 query. |

0 commit comments

Comments
 (0)