Skip to content

Commit d702527

Browse files
Merge pull request #44 from sentinel-hub/auth
Add OpenID Connect support to authentication
2 parents 5ccfc2b + 5d6b23d commit d702527

23 files changed

+1065
-116
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,4 @@ node_modules
168168
cdk.context.json
169169
*.nc
170170
.claud*
171+
openapi.json

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ repos:
1818
- id: ruff-format
1919

2020
- repo: https://github.com/pre-commit/mirrors-mypy
21-
rev: v1.3.0
21+
rev: v1.15.0
2222
hooks:
2323
- id: mypy
2424
language_version: python
2525
exclude: tests/.*
2626
additional_dependencies:
2727
- pydantic~=2.0
2828
- types-cachetools
29-
- types-attrs
29+
- attrs

CONTRIBUTING.md

+39-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,45 @@ cd titiler
1111
python -m pip install -e ".[test,dev]"
1212
```
1313

14+
**Authentication Testing with Keycloak**
15+
16+
The project includes a Keycloak instance for testing OpenID Connect authentication:
17+
18+
1. Start the development environment:
19+
```bash
20+
docker compose up
21+
```
22+
23+
2. Access Keycloak admin console at http://localhost:8082/admin
24+
- Username: `admin`
25+
- Password: `admin`
26+
27+
3. Create a new client:
28+
- Go to "Clients" → "Create client"
29+
- Client ID: `titiler-openeo`
30+
- Client type: `OpenID Connect`
31+
- Click "Next"
32+
- Enable "Client authentication"
33+
- Enable "Authorization"
34+
- Click "Save"
35+
36+
4. Configure client settings:
37+
- Valid redirect URIs: `http://localhost:8081/*` and `http://localhost:8080/*` for the openEO editor
38+
- Web origins: `http://localhost:8081` and `http://localhost:8080` for the openEO editor
39+
- Click "Save"
40+
41+
5. Create a test user:
42+
- Go to "Users" → "Add user"
43+
- Username: `test`
44+
- Email: `[email protected]`
45+
- Click "Create"
46+
- Go to "Credentials" tab
47+
- Set password: `test123`
48+
- Disable "Temporary"
49+
- Click "Save password"
50+
51+
The Keycloak server will be available at http://localhost:8082 for testing OIDC authentication flows.
52+
1453
**pre-commit**
1554

1655
This repo is set to use `pre-commit` to run *isort*, *flake8*, *pydocstring*, *black* ("uncompromising Python code formatter") and mypy when committing new code.
@@ -44,4 +83,3 @@ Actions deploys automatically for new commits.):
4483

4584
```bash
4685
mkdocs gh-deploy -f docs/mkdocs.yml
47-
```

Dockerfile

+69-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,81 @@
1-
# Dockerfile for running titiler application with uvicorn server
1+
# syntax=docker/dockerfile:1
22
ARG PYTHON_VERSION=3.11
33

4-
FROM python:${PYTHON_VERSION}-slim
4+
# Build stage
5+
FROM python:${PYTHON_VERSION}-slim AS builder
56

6-
RUN apt update && apt upgrade -y
7+
# Set build labels
8+
LABEL stage=builder
9+
LABEL org.opencontainers.image.source="https://github.com/developmentseed/titiler-openeo"
10+
LABEL org.opencontainers.image.description="TiTiler OpenEO API"
11+
LABEL org.opencontainers.image.licenses="MIT"
712

8-
# ref: https://github.com/rasterio/rasterio-wheels/issues/136, https://github.com/docker-library/python/issues/989
9-
RUN apt install -y libexpat1
13+
# Set environment variables
14+
ENV PYTHONUNBUFFERED=1 \
15+
PYTHONDONTWRITEBYTECODE=1 \
16+
PIP_NO_CACHE_DIR=1 \
17+
PIP_DISABLE_PIP_VERSION_CHECK=1
1018

11-
RUN python -m pip install uvicorn gunicorn uvicorn worker
19+
# Install build dependencies
20+
RUN apt-get update && \
21+
apt-get install -y --no-install-recommends \
22+
libexpat1 && \
23+
rm -rf /var/lib/apt/lists/*
1224

13-
# Copy files and install titiler.openeo
14-
WORKDIR /tmp
25+
# Create and activate virtual environment
26+
RUN python -m venv /opt/venv
27+
ENV PATH="/opt/venv/bin:$PATH"
1528

29+
# Install application
30+
WORKDIR /tmp
1631
COPY titiler/ titiler/
17-
COPY pyproject.toml pyproject.toml
18-
COPY README.md README.md
32+
COPY pyproject.toml .
33+
COPY README.md .
34+
RUN pip install --no-cache-dir --upgrade uvicorn PyYAML ".[pystac,oidc,postgres]"
35+
36+
# Runtime stage
37+
FROM python:${PYTHON_VERSION}-slim
38+
39+
# Set runtime labels
40+
LABEL org.opencontainers.image.source="https://github.com/developmentseed/titiler-openeo"
41+
LABEL org.opencontainers.image.description="TiTiler OpenEO API"
42+
LABEL org.opencontainers.image.licenses="MIT"
1943

20-
RUN python -m pip install --no-cache-dir --upgrade ".[pystac,postgres]"
21-
RUN rm -rf /tmp/titiler pyproject.toml README.md
44+
# Set environment variables
45+
ENV PYTHONUNBUFFERED=1 \
46+
PYTHONDONTWRITEBYTECODE=1 \
47+
PATH="/opt/venv/bin:$PATH"
2248

23-
RUN mkdir /data
49+
# Install runtime dependencies
50+
RUN apt-get update && \
51+
apt-get install -y --no-install-recommends \
52+
libexpat1 \
53+
curl && \
54+
rm -rf /var/lib/apt/lists/*
55+
56+
# Copy virtual environment from builder
57+
COPY --from=builder /opt/venv /opt/venv
58+
59+
# Create non-root user
60+
RUN useradd -m -s /bin/bash titiler && \
61+
mkdir -p /data /config
62+
COPY log_config.yaml /config/log_config.yaml
63+
RUN chown -R titiler:titiler /data /config
2464

2565
WORKDIR /app
66+
USER titiler
67+
68+
# Create data directory
69+
VOLUME /data
70+
# Create config directory and copy default config
71+
VOLUME /config
72+
73+
# Add healthcheck
74+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
75+
CMD curl --fail http://localhost:8000/api || exit 1
76+
77+
# Set default command
78+
CMD ["uvicorn", "titiler.openeo.main:app", "--host", "0.0.0.0", "--port", "80", "--log-config", "/config/log_config.yaml", "--workers", "4"]
79+
80+
# Expose port
81+
EXPOSE 80

deployment/k8s/charts/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v1
22
appVersion: 0.1.0
33
description: OpenEO by TiTiler
44
name: titiler-openeo
5-
version: 0.4.0
5+
version: 0.5.0
66
icon: https://raw.githubusercontent.com/developmentseed/titiler/main/docs/logos/TiTiler_logo_small.png
77
maintainers:
88
- name: DevelopmentSeed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
version: 1
2+
disable_existing_loggers: False
3+
formatters:
4+
default:
5+
# "()": uvicorn.logging.DefaultFormatter
6+
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
7+
access:
8+
# "()": uvicorn.logging.AccessFormatter
9+
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
10+
handlers:
11+
default:
12+
formatter: default
13+
class: logging.StreamHandler
14+
stream: ext://sys.stderr
15+
access:
16+
formatter: access
17+
class: logging.StreamHandler
18+
stream: ext://sys.stdout
19+
loggers:
20+
uvicorn.error:
21+
level: INFO
22+
handlers:
23+
- default
24+
propagate: no
25+
uvicorn.access:
26+
level: INFO
27+
handlers:
28+
- access
29+
propagate: no
30+
root:
31+
level: INFO
32+
handlers:
33+
- default
34+
propagate: no

deployment/k8s/charts/templates/configmap.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ data:
66
{{- if .Values.netrc }}
77
netrc: {{ tpl (.Values.netrc) . | quote }}
88
{{- end }}
9+
log_config.yaml: |-
10+
{{ .Files.Get .Values.logging.configFile | nindent 4 }}
911
{{- if .Values.persistence.localStoreSeed }}
1012
init_store.json: |-
1113
{{ .Files.Get .Values.persistence.localStoreSeed | nindent 4 }}

deployment/k8s/charts/templates/deployment.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ spec:
1919
{{- toYaml . | nindent 8 }}
2020
{{- end }}
2121
initContainers:
22-
{{- if .Values.persistence.localStoreSeed }}
22+
{{- if and .Values.persistence.enabled .Values.persistence.localStoreSeed }}
2323
- name: init-store
2424
image: busybox
2525
command:
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{{- if .Values.autoscaling.enabled }}
2+
apiVersion: autoscaling/v2
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ include "titiler.fullname" . }}
6+
labels:
7+
{{- include "titiler.labels" . | nindent 4 }}
8+
spec:
9+
scaleTargetRef:
10+
apiVersion: apps/v1
11+
kind: Deployment
12+
name: {{ include "titiler.fullname" . }}
13+
minReplicas: {{ .Values.autoscaling.minReplicas }}
14+
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
15+
metrics:
16+
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
17+
- type: Resource
18+
resource:
19+
name: cpu
20+
target:
21+
type: Utilization
22+
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
23+
{{- end }}
24+
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
25+
- type: Resource
26+
resource:
27+
name: memory
28+
target:
29+
type: Utilization
30+
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
31+
{{- end }}
32+
{{- end }}

deployment/k8s/charts/values.yaml

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Default values for titiler-openeo.
2-
replicaCount: 1
2+
33

44
image:
55
repository: ghcr.io/sentinel-hub/titiler-openeo
@@ -13,9 +13,11 @@ image:
1313
- "--port"
1414
- "80"
1515
- "--workers"
16-
- "1"
16+
- "4"
1717
- "--forwarded-allow-ips" # to make sure it works behind a reverse proxy
1818
- "*" # Allow all
19+
- "--log-config"
20+
- "/config/log_config.yaml"
1921

2022
nameOverride: ""
2123
fullnameOverride: ""
@@ -39,6 +41,16 @@ ingress:
3941
# hosts:
4042
# - titiler.local
4143

44+
# Autoscaling configuration
45+
autoscaling:
46+
enabled: false
47+
minReplicas: 1
48+
maxReplicas: 5
49+
targetCPUUtilizationPercentage: 80
50+
# targetMemoryUtilizationPercentage: 80
51+
52+
replicaCount: 1
53+
4254
extraHostPathMounts: []
4355
# - name: map-sources
4456
# mountPath: /map-sources/
@@ -76,6 +88,10 @@ tolerations: []
7688

7789
affinity: {}
7890

91+
# logging configuration
92+
logging:
93+
configFile: "files/log_config.yaml"
94+
7995
# Persistent storage configuration for the local database file
8096
persistence:
8197
enabled: true
@@ -107,7 +123,10 @@ securityContext: {}
107123
# runAsNonRoot: true
108124
# runAsUser: 1001
109125

110-
podSecurityContext: {}
126+
podSecurityContext:
127+
sysctls:
128+
- name: net.ipv4.ip_unprivileged_port_start
129+
value: "0"
111130
# fsGroup: 1001
112131
# runAsNonRoot: true
113132
# runAsUser: 1001

docker-compose.yml

+51-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
build:
77
context: .
88
ports:
9-
- "8081:8081"
9+
- "8081:80"
1010
environment:
1111
# GDAL Config
1212
# This option controls the default GDAL raster block cache size.
@@ -33,12 +33,19 @@ services:
3333
# - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
3434
# TiTiler STAC API Config
3535
- TITILER_OPENEO_API_DEBUG=TRUE
36-
# - TITILER_OPENEO_STAC_API_URL=https://stac.eoapi.dev
37-
# - TITILER_OPENEO_SERVICE_STORE_URL=/tmp/services/eoapi.json
36+
- TITILER_OPENEO_STAC_API_URL=https://stac.eoapi.dev
37+
- TITILER_OPENEO_SERVICE_STORE_URL=/tmp/services/eoapi.json
38+
# Keycloak Config
39+
- TITILER_OPENEO_AUTH_METHOD=oidc
40+
- TITILER_OPENEO_AUTH_OIDC_CLIENT_ID=titiler-openeo
41+
- TITILER_OPENEO_AUTH_OIDC_WK_URL=http://keycloak:8080/realms/master/.well-known/openid-configuration
42+
- TITILER_OPENEO_AUTH_OIDC_REDIRECT_URL=http://localhost:8080/
43+
- TITILER_OPENEO_AUTH_OIDC_SCOPES=openid profile email
44+
- TITILER_OPENEO_AUTH_OIDC_NAME_CLAIM=preferred_username
3845
env_file:
3946
- path: .env
4047
required: false
41-
command: ["uvicorn", "titiler.openeo.main:app", "--host", "0.0.0.0", "--port", "8081", "--workers", "4"]
48+
# command: ["uvicorn", "titiler.openeo.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
4249
volumes:
4350
- ./services:/tmp/services
4451

@@ -50,3 +57,43 @@ services:
5057
env_file:
5158
- path: .env
5259
required: false
60+
61+
keycloak_db:
62+
image: postgres:15-alpine
63+
environment:
64+
POSTGRES_DB: keycloak
65+
POSTGRES_USER: keycloak
66+
POSTGRES_PASSWORD: keycloak
67+
volumes:
68+
- keycloak_data:/var/lib/postgresql/data
69+
healthcheck:
70+
test: ["CMD-SHELL", "pg_isready -U keycloak"]
71+
interval: 10s
72+
timeout: 5s
73+
retries: 5
74+
75+
keycloak:
76+
image: quay.io/keycloak/keycloak:22.0
77+
environment:
78+
KC_DB: postgres
79+
KC_DB_URL: jdbc:postgresql://keycloak_db:5432/keycloak
80+
KC_DB_USERNAME: keycloak
81+
KC_DB_PASSWORD: keycloak
82+
KEYCLOAK_ADMIN: admin
83+
KEYCLOAK_ADMIN_PASSWORD: admin
84+
KC_HOSTNAME: localhost
85+
KC_HOSTNAME_PORT: 8082
86+
ports:
87+
- "8082:8080"
88+
depends_on:
89+
keycloak_db:
90+
condition: service_healthy
91+
command: start-dev
92+
healthcheck:
93+
test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
94+
interval: 30s
95+
timeout: 10s
96+
retries: 3
97+
98+
volumes:
99+
keycloak_data:

0 commit comments

Comments
 (0)