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

feat(release): support transactions with validation and authentication (#378), add auth requirements install for ingest-api tests (#415), update .example.env and README (#412) #416

Merged
merged 80 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
0c4ea31
Add `TransactionExtension` to STAC API
slesaad May 20, 2024
ab8ae77
Add workflow for deployment on push to branch
slesaad May 20, 2024
6866ff8
Add auth to stac API
slesaad May 20, 2024
aadf943
Update local dockerfiles
slesaad May 20, 2024
9eb26ce
Skip test for now
slesaad May 21, 2024
4e8b4aa
Fix dockerfile copy
slesaad May 21, 2024
868c8fb
Add missing env vars to lambda
slesaad May 21, 2024
2d5a97e
Fix auth import
slesaad May 21, 2024
4499085
Fix various issues - missing env vars, folders, etc
slesaad May 21, 2024
6e62e73
Add __init__.py to common
slesaad May 21, 2024
786ca9d
Copy common folder properly
slesaad May 21, 2024
20faa30
Refactor auth.py
slesaad May 21, 2024
8660d7b
Add missing authentication to PUT endpoints
slesaad May 22, 2024
b80d232
Add validation and bulk transactions
slesaad May 22, 2024
10c1cb7
Remove stac-pydantic dependency
slesaad May 22, 2024
647a07c
Build jwks url during runtime
slesaad May 22, 2024
02b22f7
Fix stage env var
slesaad May 22, 2024
5b1e2be
Update bulkitem model
slesaad May 22, 2024
fe403c6
Remove duplication boto3 install
slesaad May 22, 2024
f2f0c40
Add log
slesaad May 22, 2024
795dabf
Prepend `root_path` to url for validation
slesaad May 22, 2024
bd3a79b
Handle validation error properly
slesaad May 22, 2024
5c7245e
Add docstrings
slesaad May 22, 2024
4f10ed9
Sort
slesaad May 22, 2024
ebaca03
Make linter happy
slesaad May 22, 2024
7c547ea
Sort imports
slesaad May 22, 2024
680b39c
Merge branch 'develop' into full-transaction-support
slesaad May 28, 2024
76b0b46
Merge branch 'develop' into full-transaction-support
slesaad Jun 17, 2024
3376bb1
Update variable name
slesaad Jun 17, 2024
bd6f72f
Revert `jwks_url` to build at infrastructure
slesaad Jun 17, 2024
ffe92c5
Add feature flag for enabling transactions
slesaad Jul 23, 2024
c40a5ca
Use `pystac` for validation instead
slesaad Jul 23, 2024
e6278d8
Add pystac in setup
slesaad Jul 23, 2024
16e56c0
Add package files
slesaad Jul 24, 2024
2afe9c4
Fix veda_auth package, use it in stac and ingest
slesaad Jul 24, 2024
0563818
Fix linting errors
slesaad Jul 24, 2024
bdb70f3
Make auth optional, fix item reference in bulk ingest
slesaad Jul 24, 2024
a45f18a
Set `jwks_url` only if `userpool_id` is provided
slesaad Jul 24, 2024
5e45496
Format
slesaad Jul 24, 2024
5befeb1
Pass `enable_transactions` env var as string not bool
slesaad Jul 24, 2024
5a1eac0
Install auth before running ingest tests
slesaad Jul 24, 2024
8d202c1
Format fix :facepalm:
slesaad Jul 24, 2024
15e0286
Rename folder names
slesaad Jul 24, 2024
84c8273
Merge branch 'develop' into full-transaction-support
slesaad Jul 24, 2024
76a8132
Ceil pydantic<2
slesaad Jul 24, 2024
a2da159
Use pydantic v2
slesaad Jul 24, 2024
dd47158
Fix pin pydantic<2
slesaad Jul 25, 2024
7b885f8
Use relevant path for dockerfile
slesaad Jul 25, 2024
b106531
Match fastapi requirements
slesaad Jul 25, 2024
224de7b
Add tests for transactions
slesaad Jul 25, 2024
35efc69
Try to fix dependency issues
slesaad Jul 25, 2024
d041398
Add docstrings
slesaad Jul 25, 2024
a4337dd
Add brotli to requirements
slesaad Jul 25, 2024
5eedb56
Change order of pip installs in stac_api
slesaad Jul 26, 2024
7d11f07
Format
slesaad Jul 26, 2024
85ed997
Remove temporary workflow
slesaad Jul 26, 2024
4dacaff
Make pypgstac an ARG in dockerfile
sandrahoang686 Jul 29, 2024
0f93644
change ARG to ENV
sandrahoang686 Jul 29, 2024
358e8b7
Add env, env version may change though
sandrahoang686 Jul 29, 2024
dbcaed7
Add missing env vars and fix authentication override
slesaad Jul 30, 2024
95e76b0
Format
slesaad Jul 30, 2024
23cb5ce
format
anayeaye Jul 31, 2024
99225ad
test transactions on pr
anayeaye Jul 31, 2024
e2b1f76
Update env var to have `VEDA_STAC_` prefix
slesaad Jul 31, 2024
87ba938
Comment out stac api unit tests for now
slesaad Aug 5, 2024
ec3e160
Change ENV to ARG and add logic for build arg
sandrahoang686 Aug 5, 2024
dbeb069
docs: update .example.env and README (#412)
ciaransweet Aug 5, 2024
88dd1fb
Merge branch 'develop' into full-transaction-support
slesaad Aug 5, 2024
3b4b495
feat: support transactions with validation and authentication (#378)
slesaad Aug 6, 2024
aa25442
fix: add auth requirements install for ingest-api tests (#415)
slesaad Aug 6, 2024
485dbc9
lint
anayeaye Aug 6, 2024
e41cf82
merge conflict resolved
anayeaye Aug 6, 2024
b8ac115
feat: deterministic outputs of service urls (#411)
ciaransweet Aug 7, 2024
7769c01
fix: address dependabot alert 4 (#414)
ciaransweet Aug 8, 2024
69e8227
fix: output stac bucket name not host (#417)
ciaransweet Aug 8, 2024
ea88a2c
add pgstac version to ingest build api lambda params
anayeaye Aug 8, 2024
826f46a
lint
anayeaye Aug 8, 2024
ca008d4
Update conflict mistake and add to build_ingest
sandrahoang686 Aug 12, 2024
e6f10d6
Remove pypgstac from requirements file
sandrahoang686 Aug 12, 2024
96a336f
chore: Make pypgstac an ENV in dockerfile (#404)
sandrahoang686 Aug 14, 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
20 changes: 13 additions & 7 deletions .example.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ CDK_DEFAULT_REGION=[REQUIRED IF DEPLOYING TO EXISTING VPC]

STAGE=[FILL ME IN]

VEDA_PROJECT_NAME=
VEDA_PROJECT_DESCRIPTION=

VEDA_DB_PGSTAC_VERSION=0.6.6
VEDA_DB_PGSTAC_VERSION=0.7.10
VEDA_DB_SCHEMA_VERSION=0.1.0
VEDA_DB_SNAPSHOT_ID=[OPTIONAL BUT **REQUIRED** FOR ALL DEPLOYMENTS AFTER BASING DEPLOYMENT ON SNAPSHOT]
VEDA_DB_PUBLICLY_ACCESSIBLE=TRUE
VEDA_DB_USE_RDS_PROXY=[OPTIONAL]
VEDA_DB_RDS_INSTANCE_CLASS=[OPTIONAL]
VEDA_DB_RDS_INSTANCE_SIZE=[OPTIONAL]

VEDA_DOMAIN_HOSTED_ZONE_ID=[OPTIONAL]
VEDA_DOMAIN_HOSTED_ZONE_NAME=[OPTIONAL]
Expand All @@ -21,11 +22,16 @@ VEDA_DOMAIN_ALT_HOSTED_ZONE_NAME=[OPTIONAL SECOND DOMAIN]
VEDA_RASTER_ENABLE_MOSAIC_SEARCH=TRUE
VEDA_RASTER_DATA_ACCESS_ROLE_ARN=[OPTIONAL ARN OF IAM ROLE TO BE ASSUMED BY RASTER API]
VEDA_RASTER_EXPORT_ASSUME_ROLE_CREDS_AS_ENVS=False

VEDA_DB_PUBLICLY_ACCESSIBLE=TRUE

VEDA_RASTER_ROOT_PATH=

VEDA_STAC_ROOT_PATH=
VEDA_STAC_ENABLE_TRANSACTIONS=FALSE

VEDA_USERPOOL_ID=
VEDA_CLIENT_ID=
VEDA_CLIENT_SECRET=secret
VEDA_DATA_ACCESS_ROLE_ARN=
VEDA_COGNITO_DOMAIN=

STAC_BROWSER_BUCKET=
STAC_URL=
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
- name: Install reqs for ingest api
run: python -m pip install -r ingest_api/runtime/requirements_dev.txt

- name: Install veda auth for ingest api
run: python -m pip install common/auth

- name: Ingest unit tests
run: NO_PYDANTIC_SSM_SETTINGS=1 python -m pytest ingest_api/runtime/tests/ -vv -s

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
- name: Install reqs for ingest api
run: python -m pip install -r ingest_api/runtime/requirements_dev.txt

- name: Install veda auth for ingest api
run: python -m pip install common/auth

- name: Ingest unit tests
run: NO_PYDANTIC_SSM_SETTINGS=1 python -m pytest ingest_api/runtime/tests/ -vv -s

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,15 @@ jobs:
- name: Install reqs for ingest api
run: python -m pip install -r ingest_api/runtime/requirements_dev.txt

- name: Install veda auth for ingest api
run: python -m pip install common/auth

- name: Ingest unit tests
run: NO_PYDANTIC_SSM_SETTINGS=1 python -m pytest ingest_api/runtime/tests/ -vv -s

# - name: Stac-api transactions unit tests
# run: python -m pytest stac_api/runtime/tests/ -vv -s

- name: Stop services
run: docker compose stop

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This project uses an AWS CDK [CloudFormation](https://docs.aws.amazon.com/AWSClo

### Enviroment variables

An [.example.env](.example.env) template is supplied for for local deployments. If updating an existing deployment, it is essential to check the most current values for these variables by fetching these values from AWS Secrets Manager. The environment secrets are named `<app-name>-<stage>-env`, for example `veda-backend-dev-env`.
An [.example.env](.example.env) template is supplied for local deployments. If updating an existing deployment, it is essential to check the most current values for these variables by fetching these values from AWS Secrets Manager. The environment secrets are named `<app-name>-<stage>-env`, for example `veda-backend-dev-env`.
> **Warning** The environment variables stored as AWS secrets are manually maintained and should be reviewed before deploying updates to existing stacks.

### Fetch environment variables using AWS CLI
Expand Down Expand Up @@ -92,6 +92,8 @@ python3 -m pip install -e ".[dev,deploy,test]"
#### Run the deployment

```
# Login to ECR so that you can pull public docker images
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
# Review what infrastructure changes your deployment will cause
cdk diff
# Execute deployment and standby--security changes will require approval for deployment
Expand Down
1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""common utils shared by veda stacks"""
17 changes: 17 additions & 0 deletions common/auth/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Setup veda_auth
"""

from setuptools import find_packages, setup

inst_reqs = ["cryptography>=42.0.5", "pyjwt>=2.8.0", "fastapi", "pydantic<2"]

setup(
name="veda_auth",
version="0.0.1",
description="",
python_requires=">=3.7",
packages=find_packages(),
zip_safe=False,
install_requires=inst_reqs,
include_package_data=True,
)
5 changes: 5 additions & 0 deletions common/auth/veda_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
VEDA cognito auth
"""

from veda_auth.main import VedaAuth # noqa: F401
128 changes: 128 additions & 0 deletions common/auth/veda_auth/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Authentication handler for veda.stac and veda.ingest"""

import base64
import hashlib
import hmac
import logging
from typing import Annotated, Any, Dict

import boto3
import jwt

from fastapi import Depends, HTTPException, Security, security, status

logger = logging.getLogger(__name__)


class VedaAuth:
"""Class for handling authentication"""

def __init__(self, settings) -> None:
"""
Args:
settings: pydantic settings object containing cognito details
Returns:
None

"""
self.oauth2_scheme = security.OAuth2AuthorizationCodeBearer(
authorizationUrl=settings.cognito_authorization_url,
tokenUrl=settings.cognito_token_url,
refreshUrl=settings.cognito_token_url,
)

self.jwks_client = jwt.PyJWKClient(settings.jwks_url) # Caches JWKS

def validated_token(
token_str: Annotated[str, Security(self.oauth2_scheme)],
required_scopes: security.SecurityScopes,
) -> Dict:
# Parse & validate token
logger.info(f"\nToken String {token_str}")
try:
token = jwt.decode(
token_str,
self.jwks_client.get_signing_key_from_jwt(token_str).key,
algorithms=["RS256"],
)
except jwt.exceptions.InvalidTokenError as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
) from e

# Validate scopes (if required)
for scope in required_scopes.scopes:
if scope not in token["scope"]:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not enough permissions",
headers={
"WWW-Authenticate": f'Bearer scope="{required_scopes.scope_str}"'
},
)

return token

self.validated_token = validated_token

def get_username(
token: Annotated[Dict[Any, Any], Depends(self.validated_token)]
) -> str:
result = token["username"] if "username" in token else str(token.get("sub"))
return result

self.get_username = get_username

def _get_secret_hash(
self, username: str, client_id: str, client_secret: str
) -> str:
# A keyed-hash message authentication code (HMAC) calculated using
# the secret key of a user pool client and username plus the client
# ID in the message.
message = username + client_id
dig = hmac.new(
bytearray(client_secret, "utf-8"),
msg=message.encode("UTF-8"),
digestmod=hashlib.sha256,
).digest()
return base64.b64encode(dig).decode()

def authenticate_and_get_token(
self,
username: str,
password: str,
user_pool_id: str,
app_client_id: str,
app_client_secret: str,
) -> Dict:
"""Authenticates the credentials and returns token"""
client = boto3.client("cognito-idp")
if app_client_secret:
auth_params = {
"USERNAME": username,
"PASSWORD": password,
"SECRET_HASH": self._get_secret_hash(
username, app_client_id, app_client_secret
),
}
else:
auth_params = {
"USERNAME": username,
"PASSWORD": password,
}
try:
resp = client.admin_initiate_auth(
UserPoolId=user_pool_id,
ClientId=app_client_id,
AuthFlow="ADMIN_USER_PASSWORD_AUTH",
AuthParameters=auth_params,
)
except client.exceptions.NotAuthorizedException:
return {
"message": "Login failed, please make sure the credentials are correct."
}
except Exception as e:
return {"message": f"Login failed with exception {e}"}
return resp["AuthenticationResult"]
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ services:
- PGPASSWORD=password
- PGDATABASE=postgis
- DYNAMODB_ENDPOINT=http://localhost:8085
- VEDA_DB_PGSTAC_VERSION=0.7.10
ports:
- "8083:8083"
command: bash -c "bash /tmp/scripts/wait-for-it.sh -t 120 -h database -p 5432 && python /asset/local.py"
Expand Down
4 changes: 4 additions & 0 deletions ingest_api/infrastructure/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class IngestorConfig(BaseSettings):

ingest_root_path: str = Field("", description="Root path for ingest API")
custom_host: Optional[str] = Field(description="Custom host name")
db_pgstac_version: str = Field(
...,
description="Version of PgStac database, i.e. 0.5",
)

class Config:
case_sensitive = False
Expand Down
14 changes: 7 additions & 7 deletions ingest_api/infrastructure/construct.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(
"db_secret": db_secret,
"db_vpc": db_vpc,
"db_security_group": db_security_group,
"pgstac_version": config.db_pgstac_version,
}

if config.raster_data_access_role_arn:
Expand Down Expand Up @@ -98,21 +99,15 @@ def __init__(
custom_host=config.custom_host,
)

# CfnOutput(self, "ingest-api", value=self.api.url)
stack_name = Stack.of(self).stack_name
CfnOutput(
self,
"stac-ingestor-api-url",
export_name=f"{stack_name}-stac-ingestor-api-url",
value=self.api.url,
key="ingestapiurl",
)

register_ssm_parameter(
self,
name="jwks_url",
value=self.jwks_url,
description="JWKS URL for Cognito user pool",
)
register_ssm_parameter(
self,
name="dynamodb_table",
Expand All @@ -130,6 +125,7 @@ def build_api_lambda(
db_vpc: ec2.IVpc,
db_security_group: ec2.ISecurityGroup,
data_access_role: Union[iam.IRole, None] = None,
pgstac_version: str,
code_dir: str = "./",
) -> apigateway.LambdaRestApi:
stack_name = Stack.of(self).stack_name
Expand All @@ -156,6 +152,7 @@ def build_api_lambda(
path=os.path.abspath(code_dir),
file="ingest_api/runtime/Dockerfile",
platform="linux/amd64",
build_args={"PGSTAC_VERSION": pgstac_version},
),
runtime=aws_lambda.Runtime.PYTHON_3_9,
timeout=Duration.seconds(30),
Expand Down Expand Up @@ -297,6 +294,7 @@ def __init__(
db_vpc=db_vpc,
db_security_group=db_security_group,
db_vpc_subnets=db_vpc_subnets,
pgstac_version=config.db_pgstac_version,
)

def build_ingestor(
Expand All @@ -308,6 +306,7 @@ def build_ingestor(
db_vpc: ec2.IVpc,
db_security_group: ec2.ISecurityGroup,
db_vpc_subnets: ec2.SubnetSelection,
pgstac_version: str,
code_dir: str = "./",
) -> aws_lambda.Function:
handler = aws_lambda.Function(
Expand All @@ -317,6 +316,7 @@ def build_ingestor(
path=os.path.abspath(code_dir),
file="ingest_api/runtime/Dockerfile",
platform="linux/amd64",
build_args={"PGSTAC_VERSION": pgstac_version},
),
handler="ingestor.handler",
runtime=aws_lambda.Runtime.PYTHON_3_9,
Expand Down
9 changes: 8 additions & 1 deletion ingest_api/runtime/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
FROM public.ecr.aws/sam/build-python3.9:latest

ARG PGSTAC_VERSION
RUN echo "Using PGSTAC Version ${PGSTAC_VERSION}"

WORKDIR /tmp

COPY common/auth /tmp/common/auth
RUN pip install /tmp/common/auth -t /asset
RUN rm -rf /tmp/common

COPY ingest_api/runtime/requirements.txt /tmp/ingestor/requirements.txt
RUN pip install -r /tmp/ingestor/requirements.txt -t /asset --no-binary pydantic uvicorn
RUN pip install -r /tmp/ingestor/requirements.txt pypgstac==${PGSTAC_VERSION} -t /asset --no-binary pydantic uvicorn
RUN rm -rf /tmp/ingestor
# TODO this is temporary until we use a real packaging system like setup.py or poetry
COPY ingest_api/runtime/src /asset/src
Expand Down
4 changes: 1 addition & 3 deletions ingest_api/runtime/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Waiting for https://github.com/stac-utils/stac-pydantic/pull/116 and 117
cryptography>=42.0.5
ddbcereal==2.1.1
fastapi<=0.108.0
fastapi>=0.109.1
fsspec==2023.3.0
mangum>=0.15.0
orjson>=3.6.8
psycopg[binary,pool]>=3.0.15
pydantic_ssm_settings>=0.2.0
pydantic>=1.10.12
pyjwt>=2.8.0
pypgstac==0.7.4
python-multipart==0.0.7
requests>=2.27.1
s3fs==2023.3.0
Expand Down
Loading