Skip to content

Commit 0abcf57

Browse files
committed
More tests for authentication.
1 parent d122789 commit 0abcf57

File tree

5 files changed

+117
-193
lines changed

5 files changed

+117
-193
lines changed

charts/eoapi/profiles/experimental.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,14 @@ stac-auth-proxy:
370370
enabled: true
371371
service:
372372
port: 8080
373-
# Wait for mock-oidc to be ready in testing scenarios
373+
# Wait for dependencies to be ready before starting stac-auth-proxy
374374
initContainers:
375375
- name: wait-for-mock-oidc
376376
image: busybox:1.35
377377
command: ['sh', '-c', 'until nc -z eoapi-mock-oidc-server.eoapi.svc.cluster.local 8080; do echo waiting for mock-oidc; sleep 2; done']
378+
- name: wait-for-stac
379+
image: busybox:1.35
380+
command: ['sh', '-c', 'until nc -z eoapi-stac.eoapi.svc.cluster.local 8080; do echo waiting for stac service; sleep 2; done']
378381
env:
379382
UPSTREAM_URL: "http://eoapi-stac:8080"
380383
# For testing one could deploy a mock OIDC server (https://github.com/alukach/mock-oidc-server)

charts/eoapi/templates/core/stac-auth-proxy-patch.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ spec:
2727
- name: patch-deployment
2828
image: bitnami/kubectl:latest
2929
imagePullPolicy: IfNotPresent
30+
# Use python3 for JSON manipulation (bitnami/kubectl includes python3)
3031
command:
3132
- /bin/bash
3233
- -c
@@ -45,7 +46,8 @@ spec:
4546
exit 0
4647
fi
4748
echo "Patching deployment with initContainers..."
48-
INIT_CONTAINERS_JSON='{{ index .Values "stac-auth-proxy" "initContainers" | toJson }}'
49+
# Use Helm templating to replace service names in the JSON string
50+
INIT_CONTAINERS_JSON='{{ index .Values "stac-auth-proxy" "initContainers" | toJson | replace "eoapi-stac.eoapi" (printf "%s-stac.%s" .Release.Name .Release.Namespace) | replace "eoapi-mock-oidc-server.eoapi" (printf "%s-mock-oidc-server.%s" .Release.Name .Release.Namespace) }}'
4951
kubectl patch deployment "$DEPLOYMENT_NAME" -n "$NAMESPACE" --type='json' -p="[{\"op\":\"add\",\"path\":\"/spec/template/spec/initContainers\",\"value\":$INIT_CONTAINERS_JSON}]"
5052
echo "Waiting for rollout..."
5153
kubectl rollout status deployment/"$DEPLOYMENT_NAME" -n "$NAMESPACE" --timeout=300s

scripts/deploy.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,13 @@ deploy_eoapi() {
393393

394394
# Set UPSTREAM_URL and OIDC_DISCOVERY_URL dynamically for stac-auth-proxy when experimental profile is used
395395
# The experimental profile enables stac-auth-proxy, so we need to set the correct service names
396+
# Also configure STAC service to run without root path when behind auth proxy
396397
HELM_CMD="$HELM_CMD --set stac-auth-proxy.env.UPSTREAM_URL=http://$RELEASE_NAME-stac:8080"
397398
HELM_CMD="$HELM_CMD --set stac-auth-proxy.env.OIDC_DISCOVERY_URL=http://$RELEASE_NAME-mock-oidc-server.$NAMESPACE.svc.cluster.local:8080/.well-known/openid-configuration"
399+
# Note: initContainer service names are dynamically replaced by the stac-auth-proxy-patch job
400+
# Configure STAC service to run without root path when behind auth proxy
401+
# Empty string makes STAC service run at root path (no --root-path argument)
402+
HELM_CMD="$HELM_CMD --set 'stac.overrideRootPath='"
398403

399404
HELM_CMD="$HELM_CMD --set eoapi-notifier.enabled=true"
400405
HELM_CMD="$HELM_CMD --set eoapi-notifier.config.sources[0].config.connection.existingSecret.name=$RELEASE_NAME-pguser-eoapi"

scripts/deployment.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,14 @@ run_deployment() {
8686

8787
# Set UPSTREAM_URL and OIDC_DISCOVERY_URL dynamically for stac-auth-proxy when experimental profile is used
8888
# The experimental profile enables stac-auth-proxy, so we need to set the correct service names
89+
# Also configure STAC service to run without root path when behind auth proxy
8990
if [[ "$use_experimental" == "true" ]]; then
9091
helm_cmd="$helm_cmd --set stac-auth-proxy.env.UPSTREAM_URL=http://$RELEASE_NAME-stac:8080"
9192
helm_cmd="$helm_cmd --set stac-auth-proxy.env.OIDC_DISCOVERY_URL=http://$RELEASE_NAME-mock-oidc-server.$NAMESPACE.svc.cluster.local:8080/.well-known/openid-configuration"
93+
# Note: initContainer service names are dynamically replaced by the stac-auth-proxy-patch job
94+
# Configure STAC service to run without root path when behind auth proxy
95+
# Empty string makes STAC service run at root path (no --root-path argument)
96+
helm_cmd="$helm_cmd --set 'stac.overrideRootPath='"
9297
fi
9398

9499
if is_ci; then

tests/integration/test_stac_auth.py

Lines changed: 100 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import json
44
import os
55
import subprocess
6-
import time
7-
from datetime import datetime, timedelta
86
from typing import Generator
97

108
import httpx
@@ -19,12 +17,11 @@
1917

2018
@pytest.fixture(scope="module")
2119
def mock_oidc_server() -> Generator[str, None, None]:
22-
"""Use or deploy mock OIDC server for testing."""
20+
"""Use Helm-deployed mock OIDC server for testing."""
2321
namespace = os.getenv("NAMESPACE", "eoapi")
2422
release_name = os.getenv("RELEASE_NAME", "eoapi")
25-
deployed_by_fixture = False
2623

27-
# Check if mock OIDC server is already deployed by helm
24+
# Check if mock OIDC server is deployed by Helm
2825
result = subprocess.run(
2926
[
3027
"kubectl",
@@ -40,201 +37,39 @@ def mock_oidc_server() -> Generator[str, None, None]:
4037
text=True,
4138
)
4239

43-
if result.returncode == 0:
44-
# Mock OIDC server is already deployed by helm
45-
deployment = json.loads(result.stdout)
46-
if deployment.get("status", {}).get("readyReplicas", 0) > 0:
47-
# Server is ready, use it
48-
oidc_url = f"http://{release_name}-mock-oidc-server.{namespace}.svc.cluster.local:8080"
49-
yield oidc_url
50-
return # Don't cleanup helm-deployed server
51-
52-
# If not deployed by helm, deploy it manually for testing
53-
deployed_by_fixture = True
54-
deployment_yaml = f"""
55-
apiVersion: apps/v1
56-
kind: Deployment
57-
metadata:
58-
name: mock-oidc-server
59-
namespace: {namespace}
60-
spec:
61-
replicas: 1
62-
selector:
63-
matchLabels:
64-
app: mock-oidc-server
65-
template:
66-
metadata:
67-
labels:
68-
app: mock-oidc-server
69-
spec:
70-
containers:
71-
- name: mock-oidc
72-
image: ghcr.io/alukach/mock-oidc-server:latest
73-
env:
74-
- name: MOCK_OIDC_PORT
75-
value: "8888"
76-
- name: MOCK_OIDC_CLIENT_ID
77-
value: "test-client"
78-
- name: MOCK_OIDC_CLIENT_SECRET
79-
value: "test-secret"
80-
ports:
81-
- containerPort: 8888
82-
---
83-
apiVersion: v1
84-
kind: Service
85-
metadata:
86-
name: mock-oidc-server
87-
namespace: {namespace}
88-
spec:
89-
selector:
90-
app: mock-oidc-server
91-
ports:
92-
- port: 8080
93-
targetPort: 8888
94-
"""
95-
96-
# Apply deployment
97-
result = subprocess.run(
98-
["kubectl", "apply", "-f", "-"],
99-
input=deployment_yaml,
100-
text=True,
101-
capture_output=True,
102-
)
10340
if result.returncode != 0:
104-
pytest.skip(f"Failed to deploy mock OIDC server: {result.stderr}")
105-
106-
# Wait for pod to be ready
107-
for _ in range(30):
108-
result = subprocess.run(
109-
[
110-
"kubectl",
111-
"get",
112-
"pods",
113-
"-n",
114-
namespace,
115-
"-l",
116-
"app=mock-oidc-server",
117-
"--field-selector=status.phase=Running",
118-
"-o",
119-
"json",
120-
],
121-
capture_output=True,
122-
text=True,
123-
)
124-
if result.returncode == 0:
125-
pods = json.loads(result.stdout)
126-
if pods.get("items"):
127-
break
128-
time.sleep(2)
129-
else:
130-
pytest.skip("Mock OIDC server failed to start")
131-
132-
# Update stac-auth-proxy to use mock OIDC server (only if deployed by fixture)
133-
oidc_url = f"http://mock-oidc-server.{namespace}.svc.cluster.local:8080"
134-
if deployed_by_fixture:
135-
subprocess.run(
136-
[
137-
"kubectl",
138-
"set",
139-
"env",
140-
f"deployment/{release_name}-stac-auth-proxy",
141-
f"OIDC_DISCOVERY_URL={oidc_url}/.well-known/openid-configuration",
142-
"DEFAULT_PUBLIC=true",
143-
"-n",
144-
namespace,
145-
],
146-
capture_output=True,
41+
pytest.skip(
42+
"Mock OIDC server not deployed. Enable mockOidcServer in Helm values."
14743
)
14844

149-
# Wait for stac-auth-proxy to restart and become ready
150-
time.sleep(5)
151-
for _ in range(30):
152-
result = subprocess.run(
153-
[
154-
"kubectl",
155-
"get",
156-
"pods",
157-
"-n",
158-
namespace,
159-
"-l",
160-
f"app.kubernetes.io/instance={release_name},app.kubernetes.io/name=stac-auth-proxy",
161-
"--field-selector=status.phase=Running",
162-
"-o",
163-
"json",
164-
],
165-
capture_output=True,
166-
text=True,
167-
)
168-
if result.returncode == 0:
169-
pods = json.loads(result.stdout)
170-
if pods.get("items") and len(pods["items"]) > 0:
171-
# Check if auth proxy started successfully with new config
172-
result = subprocess.run(
173-
[
174-
"kubectl",
175-
"logs",
176-
f"deployment/{release_name}-stac-auth-proxy",
177-
"-n",
178-
namespace,
179-
"--tail=5",
180-
],
181-
capture_output=True,
182-
text=True,
183-
)
184-
if "Application startup complete" in result.stdout:
185-
break
186-
time.sleep(2)
45+
# Verify the server is ready
46+
deployment = json.loads(result.stdout)
47+
if deployment.get("status", {}).get("readyReplicas", 0) == 0:
48+
pytest.skip("Mock OIDC server not ready")
18749

50+
# Return the service URL - no manual deployment or cleanup needed
51+
oidc_url = f"http://{release_name}-mock-oidc-server.{namespace}.svc.cluster.local:8080"
18852
yield oidc_url
18953

190-
# Cleanup only if deployed by this fixture
191-
if deployed_by_fixture:
192-
subprocess.run(
193-
[
194-
"kubectl",
195-
"delete",
196-
"deployment",
197-
"mock-oidc-server",
198-
"-n",
199-
namespace,
200-
],
201-
capture_output=True,
202-
)
203-
subprocess.run(
204-
[
205-
"kubectl",
206-
"delete",
207-
"service",
208-
"mock-oidc-server",
209-
"-n",
210-
namespace,
211-
],
212-
capture_output=True,
213-
)
214-
21554

21655
@pytest.fixture
217-
def valid_token(mock_oidc_server: str) -> str:
218-
"""Generate valid JWT token."""
219-
try:
220-
import jwt
221-
except ImportError:
222-
pytest.skip("pyjwt not installed")
56+
def dummy_token(mock_oidc_server: str) -> str:
57+
"""Create a dummy JWT token for testing auth proxy behavior.
58+
59+
This is a dummy token used to test that the auth proxy is correctly
60+
processing Authorization headers and rejecting invalid tokens.
61+
62+
Args:
63+
mock_oidc_server: URL of the mock OIDC server (unused but required for fixture)
22364
224-
payload = {
225-
"sub": "test-user",
226-
"email": "[email protected]",
227-
"iss": mock_oidc_server,
228-
"aud": "test-client",
229-
"exp": datetime.utcnow() + timedelta(hours=1),
230-
"iat": datetime.utcnow(),
231-
}
232-
# Mock OIDC server uses a simple secret
233-
token = jwt.encode(payload, "test-secret", algorithm="HS256")
234-
# Handle both str (newer PyJWT) and bytes (older PyJWT) return types
235-
if isinstance(token, bytes):
236-
return token.decode("utf-8")
237-
return str(token)
65+
Returns:
66+
A dummy Bearer token string for testing
67+
68+
Note:
69+
The actual token validation will fail, which is the expected behavior
70+
since creating valid OIDC tokens requires complex authorization flows.
71+
"""
72+
return "Bearer dummy-token-for-testing"
23873

23974

24075
def test_stac_write_create_item_without_token(
@@ -271,3 +106,77 @@ def test_stac_write_create_item_without_token(
271106
assert resp.status_code in [401, 403], (
272107
f"Expected 401 Unauthorized or 403 Forbidden, got {resp.status_code}"
273108
)
109+
110+
111+
def test_stac_write_create_item_with_dummy_token(
112+
stac_endpoint: str, mock_oidc_server: str, dummy_token: str
113+
) -> None:
114+
"""Test that auth proxy correctly processes and rejects invalid tokens.
115+
116+
This test verifies the auth proxy is working by sending a request with
117+
an invalid Authorization header. The expected behavior is rejection
118+
with 401 or 403 status code.
119+
120+
Args:
121+
stac_endpoint: The STAC API endpoint URL
122+
mock_oidc_server: URL of the mock OIDC server (for test context)
123+
dummy_token: A dummy Bearer token for testing
124+
125+
Validates:
126+
- Auth proxy intercepts requests with Authorization headers
127+
- Invalid tokens are properly rejected (401/403 response)
128+
- Auth system is functioning correctly
129+
"""
130+
# Attempt to create an item with invalid authorization
131+
headers = {"Authorization": dummy_token}
132+
resp = client.post(
133+
f"{stac_endpoint}/collections/noaa-emergency-response/items",
134+
headers=headers,
135+
json={
136+
"id": "test-auth-item-with-dummy-token",
137+
"type": "Feature",
138+
"stac_version": "1.0.0",
139+
"properties": {"datetime": "2024-01-01T00:00:00Z"},
140+
"geometry": {"type": "Point", "coordinates": [0, 0]},
141+
"links": [],
142+
"assets": {},
143+
"collection": "noaa-emergency-response",
144+
"bbox": [-0.1, -0.1, 0.1, 0.1],
145+
},
146+
)
147+
148+
# Verify auth proxy correctly rejects invalid token
149+
assert resp.status_code in [401, 403], (
150+
f"Expected 401 Unauthorized or 403 Forbidden with invalid token, "
151+
f"got {resp.status_code}. This indicates the auth proxy may not be "
152+
f"properly validating Authorization headers."
153+
)
154+
155+
156+
def test_auth_proxy_integration_with_mock_oidc() -> None:
157+
"""Integration test to verify auth proxy can communicate with mock OIDC server.
158+
159+
This test verifies that the auth proxy can successfully:
160+
1. Connect to the mock OIDC server
161+
2. Retrieve OIDC configuration
162+
3. Access JWKS for token validation
163+
4. Validate properly signed JWT tokens
164+
5. Allow authenticated write operations
165+
166+
Implementation approaches:
167+
- Run tests in-cluster (e.g., as a Job or from another pod)
168+
- Use kubectl port-forward to expose mock OIDC server
169+
- Extract and use the mock server's actual RSA private key
170+
- Create tokens using the server's token generation endpoint
171+
172+
Note: This test is currently skipped because it requires network access to
173+
internal k8s services that aren't available from external test environments.
174+
"""
175+
pytest.skip(
176+
"This test requires network access to internal k8s services. "
177+
"To implement:\n"
178+
"1. Set up kubectl port-forward for mock OIDC server\n"
179+
"2. Get valid token from http://localhost:PORT/ endpoint\n"
180+
"3. Test STAC write operations with valid Authorization header\n"
181+
"4. Verify 200/201 responses for authenticated requests"
182+
)

0 commit comments

Comments
 (0)