Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional auth tooling #8

Merged
merged 48 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7273fb2
Add optional auth tooling
alukach Jul 18, 2024
c64186c
Fix imports
alukach Jul 24, 2024
b542594
Run pre-commit
alukach Jul 24, 2024
66830f5
Refactor
alukach Jul 26, 2024
2a26318
Use PKCE by default
alukach Jul 26, 2024
7fa43d1
Update auth.py
alukach Jul 26, 2024
aec1edb
Merge branch 'main' into feature/add-auth
alukach Aug 2, 2024
54d4eda
Working OIDC example
alukach Aug 7, 2024
b4e4353
Support OIDC in stac-browser
alukach Aug 7, 2024
31b60f5
Cleanup
alukach Aug 7, 2024
0d79d87
Add logging
alukach Aug 7, 2024
a08b758
In progress auth for raster
alukach Aug 7, 2024
58e256b
Raster: Finalize / cleanup
alukach Aug 8, 2024
886ca38
STAC: cleanup
alukach Aug 8, 2024
996f5a6
Vector: add auth support
alukach Aug 8, 2024
c0d699a
Raster: add client id to swagger ui
alukach Aug 8, 2024
0dc2503
Cleanup
alukach Aug 8, 2024
8ef74c7
STAC: Use same auth module as others
alukach Aug 8, 2024
d3a1bc0
Rename
alukach Aug 8, 2024
c92bb11
Cleanup imports
alukach Aug 9, 2024
a836183
Auth: Update logging
alukach Aug 9, 2024
b24c612
Don't buffer Python output
alukach Aug 9, 2024
74fcfc3
Add logging, refactor imports
alukach Aug 9, 2024
357809b
Undo .env & pythonbuffered changes
alukach Aug 10, 2024
3587e2b
Raster: Rm all but auth changes
alukach Aug 10, 2024
9d28d25
Stac: Rm all but required auth code
alukach Aug 10, 2024
0a01be1
Revert unnecessary gitignore change
alukach Aug 10, 2024
ac41c1b
Vector: fixup imports
alukach Aug 10, 2024
d87f15c
Precommit: fix imports
alukach Aug 10, 2024
4975469
Pre-commit: fix titiler extension
alukach Aug 10, 2024
26fe978
Add stac browser config to env example
alukach Aug 10, 2024
c248b7b
Merge branch 'main' into feature/add-auth
alukach Aug 12, 2024
92f4465
Pre-commit fix
alukach Aug 12, 2024
b784e1c
Simplify
alukach Aug 14, 2024
1a71e98
Merge branch 'main' of https://github.com/developmentseed/eoapi-devse…
vincentsarago Aug 19, 2024
94628bd
Rm version (deprecated)
alukach Aug 19, 2024
aa30f7d
Breakout auth tooling into separate module
alukach Aug 19, 2024
78b75a0
Breakout into files
alukach Aug 19, 2024
4996c8b
Rename things
alukach Aug 19, 2024
ca88be8
Fix version path
alukach Aug 19, 2024
51e58d7
Apply suggestions from code review
alukach Aug 20, 2024
a7e6333
Mv dependency
alukach Aug 19, 2024
f1037a9
Use published eoapi.auth-utils pkg
alukach Aug 20, 2024
2e11d2f
Rework imports
alukach Aug 21, 2024
9810b7d
Rework imports
alukach Aug 21, 2024
a7b0163
Upgrade auth dep, use convenience method
alukach Aug 21, 2024
896f185
Simplify (rm concept of public_reads)
alukach Aug 21, 2024
6a14ee1
AuthSettings -> OpenIdConnectSettings
vincentsarago Aug 22, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ VSI_CACHE_SIZE=536870912
MOSAIC_CONCURRENCY=1
EOAPI_RASTER_ENABLE_MOSAIC_SEARCH=TRUE

# AUTH
EOAPI_AUTH_CLIENT_ID=my-client-id
EOAPI_AUTH_OPENID_CONFIGURATION_URL=https://cognito-idp.us-east-1.amazonaws.com/<user pool id>/.well-known/openid-configuration
EOAPI_AUTH_USE_PKCE=true
SB_authConfig={ "type": "openIdConnect", "openIdConnectUrl": "https://cognito-idp.us-east-1.amazonaws.com/<user pool id>/.well-known/openid-configuration", "oidcOptions": { "client_id": "stac-browser" } }
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ jobs:
# see https://github.com/developmentseed/tipg/issues/37
- name: Restart the Vector service
run: |
docker compose stop vector
docker compose up -d vector
docker compose restart vector

- name: Sleep for 10 seconds
run: sleep 10s
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: "3"

services:
# change to official image when available https://github.com/radiantearth/stac-browser/pull/386
stac-browser:
# build: https://github.com/radiantearth/stac-browser.git
# TODO: Rm when https://github.com/radiantearth/stac-browser/pull/461 is merged
build:
context: dockerfiles
dockerfile: Dockerfile.browser
Expand Down
4 changes: 2 additions & 2 deletions dockerfiles/Dockerfile.browser
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN rm config.js
RUN npm install
# replace the default config.js with our config file
COPY ./browser_config.js ./config.js
RUN \[ "${DYNAMIC_CONFIG}" == "true" \] && sed -i 's/<!-- <script defer="defer" src=".\/config.js"><\/script> -->/<script defer="defer" src=".\/config.js"><\/script>/g' public/index.html
RUN \[ "${DYNAMIC_CONFIG}" == "true" \] && sed -i "s|<!-- <script defer=\"defer\" src=\"/config.js\"></script> -->|<script defer=\"defer\" src=\"${pathPrefix}config.js\"></script>|g" public/index.html
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical for getting custom config working.

RUN npm run build


Expand All @@ -31,4 +31,4 @@ EXPOSE 8085
STOPSIGNAL SIGTERM

# override entrypoint, which calls nginx-entrypoint underneath
COPY --from=build-step /app/docker/docker-entrypoint.sh ./docker-entrypoint.d/40-stac-browser-entrypoint.sh
ADD ./docker-entrypoint.sh ./docker-entrypoint.d/40-stac-browser-entrypoint.sh
96 changes: 96 additions & 0 deletions dockerfiles/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# TODO: Rm when https://github.com/radiantearth/stac-browser/pull/461 is merged
# echo a string, handling different types
safe_echo() {
# $1 = value
if [ -z "$1" ]; then
echo -n "null"
elif printf '%s\n' "$1" | grep -qE '\n.+\n$'; then
echo -n "\`$1\`"
else
echo -n "'$1'"
fi
}

# handle boolean
bool() {
# $1 = value
case "$1" in
true | TRUE | yes | t | True)
echo -n true
;;
false | FALSE | no | n | False)
echo -n false
;;
*)
echo "Err: Unknown boolean value \"$1\"" >&2
exit 1
;;
esac
}

# handle array values
array() {
# $1 = value
# $2 = arraytype
if [ -z "$1" ]; then
echo -n "[]"
else
case "$2" in
string)
echo -n "['$(echo "$1" | sed "s/,/', '/g")']"
;;
*)
echo -n "[$1]"
;;
esac
fi
}

# handle object values
object() {
# $1 = value
if [ -z "$1" ]; then
echo -n "null"
else
echo -n "$1"
fi
}

config_schema=$(cat /etc/nginx/conf.d/config.schema.json)

# Iterate over environment variables with "SB_" prefix
env -0 | cut -f1 -d= | tr '\0' '\n' | grep "^SB_" | {
echo "window.STAC_BROWSER_CONFIG = {"
while IFS='=' read -r name; do
# Strip the prefix
argname="${name#SB_}"
# Read the variable's value
value="$(eval "echo \"\$$name\"")"

# Get the argument type from the schema
argtype="$(echo "$config_schema" | jq -r ".properties.$argname.type[0]")"
arraytype="$(echo "$config_schema" | jq -r ".properties.$argname.items.type[0]")"

# Encode key/value
echo -n " $argname: "
case "$argtype" in
string)
safe_echo "$value"
;;
boolean)
bool "$value"
;;
integer | number | object)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Customization added in radiantearth/stac-browser#461

object "$value"
;;
array)
array "$value" "$arraytype"
;;
*)
safe_echo "$value"
;;
esac
echo ","
done
echo "}"
} >/usr/share/nginx/html/config.js
27 changes: 24 additions & 3 deletions runtimes/eoapi/raster/eoapi/raster/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import jinja2
import pystac
from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
from fastapi import Depends, FastAPI, Query
from psycopg import OperationalError
from psycopg.rows import dict_row
Expand Down Expand Up @@ -38,12 +39,15 @@
from titiler.pgstac.reader import PgSTACReader

from . import __version__ as eoapi_raster_version
from . import config, logs
from .config import ApiSettings
from .logs import init_logging

settings = ApiSettings()
auth_settings = OpenIdConnectSettings()

settings = config.ApiSettings()

# Logs
logs.init_logging(
init_logging(
debug=settings.debug,
loggers={
"botocore.credentials": {
Expand Down Expand Up @@ -95,6 +99,10 @@ async def lifespan(app: FastAPI):
docs_url="/api.html",
root_path=settings.root_path,
lifespan=lifespan,
swagger_ui_init_oauth={
"clientId": auth_settings.client_id,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when client_id is set to "" does Swagger understand that its unavailable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clientId property populates the value on the auth form's text input (docs). As such, an empty string is equivalent to null

"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
},
)
add_exception_handlers(app, DEFAULT_STATUS_CODES)
add_exception_handlers(app, MOSAIC_STATUS_CODES)
Expand Down Expand Up @@ -404,3 +412,16 @@ def landing(request: Request):
"urlparams": str(request.url.query),
},
)


# Add dependencies to routes
if auth_settings.openid_configuration_url:
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)

restricted_prefixes = ["/collections", "/searches"]
for route in app.routes:
if any(
route.path.startswith(f"{app.root_path}{prefix}")
for prefix in restricted_prefixes
):
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])
1 change: 1 addition & 0 deletions runtimes/eoapi/raster/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies = [
"titiler.extensions",
"starlette-cramjam>=0.3,<0.4",
"importlib_resources>=1.1.0;python_version<'3.9'",
"eoapi.auth-utils>=0.2.0",
]

[project.optional-dependencies]
Expand Down
28 changes: 24 additions & 4 deletions runtimes/eoapi/stac/eoapi/stac/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
from contextlib import asynccontextmanager

from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from stac_fastapi.api.app import StacApi
Expand Down Expand Up @@ -34,7 +35,9 @@
from starlette.templating import Jinja2Templates
from starlette_cramjam.middleware import CompressionMiddleware

from . import config, extension, logs
from .config import ApiSettings
from .extension import TiTilerExtension
from .logs import init_logging

try:
from importlib.resources import files as resources_files # type: ignore
Expand All @@ -45,11 +48,12 @@

templates = Jinja2Templates(directory=str(resources_files(__package__) / "templates")) # type: ignore

api_settings = config.ApiSettings()
api_settings = ApiSettings()
auth_settings = OpenIdConnectSettings()
settings = Settings(enable_response_models=True)

# Logs
logs.init_logging(debug=api_settings.debug)
init_logging(debug=api_settings.debug)
logger = logging.getLogger(__name__)

# Extensions
Expand All @@ -66,7 +70,7 @@
"filter": FilterExtension(client=FiltersClient()),
"bulk_transactions": BulkTransactionExtension(client=BulkTransactionsClient()),
"titiler": (
extension.TiTilerExtension(titiler_endpoint=api_settings.titiler_endpoint)
TiTilerExtension(titiler_endpoint=api_settings.titiler_endpoint)
if api_settings.titiler_endpoint
else None
),
Expand Down Expand Up @@ -129,6 +133,10 @@ async def lifespan(app: FastAPI):
openapi_url="/api",
docs_url="/api.html",
redoc_url=None,
swagger_ui_init_oauth={
"clientId": auth_settings.client_id,
"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
},
),
title=api_settings.name,
description=api_settings.name,
Expand All @@ -155,3 +163,15 @@ async def viewer_page(request: Request):
},
media_type="text/html",
)


if auth_settings.openid_configuration_url:
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)

restricted_prefixes = ["/collections", "/search"]
for route in app.routes:
if any(
route.path.startswith(f"{app.root_path}{prefix}")
for prefix in restricted_prefixes
):
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])
1 change: 1 addition & 0 deletions runtimes/eoapi/stac/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies = [
"starlette-cramjam>=0.3,<0.4",
"importlib_resources>=1.1.0;python_version<'3.9'",
"psycopg_pool",
"eoapi.auth-utils>=0.2.0",
]

[project.optional-dependencies]
Expand Down
25 changes: 22 additions & 3 deletions runtimes/eoapi/vector/eoapi/vector/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from contextlib import asynccontextmanager

import jinja2
from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
from fastapi import FastAPI, Request
from starlette.middleware.cors import CORSMiddleware
from starlette.templating import Jinja2Templates
Expand All @@ -16,7 +17,8 @@
from tipg.settings import PostgresSettings

from . import __version__ as eoapi_vector_version
from . import config, logs
from .config import ApiSettings
from .logs import init_logging

try:
from importlib.resources import files as resources_files # type: ignore
Expand All @@ -27,11 +29,12 @@

CUSTOM_SQL_DIRECTORY = resources_files(__package__) / "sql"

settings = config.ApiSettings()
settings = ApiSettings()
postgres_settings = PostgresSettings()
auth_settings = OpenIdConnectSettings()

# Logs
logs.init_logging(
init_logging(
debug=settings.debug,
loggers={
"botocore.credentials": {
Expand Down Expand Up @@ -88,6 +91,10 @@ async def lifespan(app: FastAPI):
docs_url="/api.html",
lifespan=lifespan,
root_path=settings.root_path,
swagger_ui_init_oauth={
"clientId": auth_settings.client_id,
"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
},
)

# add eoapi_vector templates and tipg templates
Expand Down Expand Up @@ -168,3 +175,15 @@ async def refresh(request: Request):
)

return request.app.state.collection_catalog


if auth_settings.openid_configuration_url:
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)

restricted_prefixes = ["/collections"]
for route in app.routes:
if any(
route.path.startswith(f"{app.root_path}{prefix}")
for prefix in restricted_prefixes
):
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])
1 change: 1 addition & 0 deletions runtimes/eoapi/vector/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ classifiers = [
dynamic = ["version"]
dependencies = [
"tipg==0.7.1",
"eoapi.auth-utils>=0.2.0",
]

[project.optional-dependencies]
Expand Down