From ca665079074623d5e2da2f5677646d20fc6de900 Mon Sep 17 00:00:00 2001 From: Jennifer Tran Date: Thu, 25 Apr 2024 11:48:41 -0700 Subject: [PATCH 1/4] feat: implement new auth flow --- infrastructure/.terraform.lock.hcl | 5 ++ infrastructure/main.tf | 8 +- infrastructure/terraform.tfvars.tmpl | 3 + infrastructure/variables.tf | 11 +++ workflows_api/runtime/requirements.txt | 2 + workflows_api/runtime/src/auth.py | 101 +++++++++++------------- workflows_api/runtime/src/config.py | 24 +++++- workflows_api/runtime/src/main.py | 30 +++++-- workflows_api/runtime/src/validators.py | 3 +- 9 files changed, 120 insertions(+), 67 deletions(-) diff --git a/infrastructure/.terraform.lock.hcl b/infrastructure/.terraform.lock.hcl index 61c6933c..30fdcc63 100644 --- a/infrastructure/.terraform.lock.hcl +++ b/infrastructure/.terraform.lock.hcl @@ -4,6 +4,7 @@ provider "registry.terraform.io/hashicorp/archive" { version = "2.4.0" hashes = [ + "h1:ZtsrX5F13Ohsv/k/BvgyBVn0gF+lW4bkG7JmCGrN35Y=", "h1:cJokkjeH1jfpG4QEHdRx0t2j8rr52H33A7C/oX73Ok4=", "zh:18e408596dd53048f7fc8229098d0e3ad940b92036a24287eff63e2caec72594", "zh:392d4216ecd1a1fd933d23f4486b642a8480f934c13e2cae3c13b6b6a7e34a7b", @@ -25,6 +26,7 @@ provider "registry.terraform.io/hashicorp/aws" { constraints = "~> 4.0, >= 4.54.0" hashes = [ "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", + "h1:P43vwcDPG99x5WBbmqwUPgfJrfXf6/ucAIbGlRb7k1w=", "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", @@ -47,6 +49,7 @@ provider "registry.terraform.io/hashicorp/external" { version = "2.3.1" constraints = ">= 1.0.0" hashes = [ + "h1:9rJggijNdRdFk//ViQPGZdK0xu9XU/9qBDijNsZJMg0=", "h1:gznGscVJ0USxy4CdihpjRKPsKvyGr/zqPvBoFLJTQDc=", "zh:001e2886dc81fc98cf17cf34c0d53cb2dae1e869464792576e11b0f34ee92f54", "zh:2eeac58dd75b1abdf91945ac4284c9ccb2bfb17fa9bdb5f5d408148ff553b3ee", @@ -67,6 +70,7 @@ provider "registry.terraform.io/hashicorp/local" { version = "2.4.0" constraints = ">= 1.0.0" hashes = [ + "h1:Bs7LAkV/iQTLv72j+cTMrvx2U3KyXrcVHaGbdns1NcE=", "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=", "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9", "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf", @@ -87,6 +91,7 @@ provider "registry.terraform.io/hashicorp/null" { version = "3.2.1" constraints = ">= 2.0.0" hashes = [ + "h1:tSj1mL6OQ8ILGqR2mDu7OYYYWf+hoir0pf9KAQ8IzO8=", "h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=", "zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840", "zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb", diff --git a/infrastructure/main.tf b/infrastructure/main.tf index b6e23099..18d0633d 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -196,7 +196,10 @@ resource "aws_iam_role_policy_attachment" "lambda_basic_execution" { policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } - +# Function to build the JWKS URL +locals { + build_jwks_url = "${format("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", local.aws_region, var.userpool_id)}" +} resource "aws_lambda_function" "workflows_api_handler" { function_name = "${var.prefix}_workflows_api_handler" role = aws_iam_role.lambda_execution_role.arn @@ -213,6 +216,9 @@ resource "aws_lambda_function" "workflows_api_handler" { RASTER_URL = var.raster_url STAC_URL = var.stac_url MWAA_ENV = "${var.prefix}-mwaa" + COGNITO_DOMAIN = var.cognito_domain + CLIENT_ID = var.client_id + JWKS_URL = local.build_jwks_url } } } diff --git a/infrastructure/terraform.tfvars.tmpl b/infrastructure/terraform.tfvars.tmpl index 4d12ddbd..930ee54d 100644 --- a/infrastructure/terraform.tfvars.tmpl +++ b/infrastructure/terraform.tfvars.tmpl @@ -16,3 +16,6 @@ vector_security_group="${VECTOR_SECURITY_GROUP}" vector_vpc="${VECTOR_VPC:-null}" workflow_root_path="${WORKFLOW_ROOT_PATH}" cloudfront_id="${CLOUDFRONT_ID}" +cognito_domain="${VEDA_COGNITO_DOMAIN}" +client_id="${VEDA_CLIENT_ID}" +userpool_id="${VEDA_USERPOOL_ID}" diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index e88da20c..6d773dc9 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -94,3 +94,14 @@ variable "cloudfront_id" { type = string } +variable "cognito_domain" { + type = string +} + +variable "client_id" { + type = string +} + +variable "userpool_id" { + type = string +} \ No newline at end of file diff --git a/workflows_api/runtime/requirements.txt b/workflows_api/runtime/requirements.txt index aeacc812..c9a8c78f 100644 --- a/workflows_api/runtime/requirements.txt +++ b/workflows_api/runtime/requirements.txt @@ -8,6 +8,7 @@ orjson>=3.6.8 psycopg[binary,pool]>=3.0.15 pydantic_ssm_settings>=0.2.0 pydantic>=1.9.0 +pyjwt>=2.8.0 pypgstac==0.7.4 python-multipart==0.0.5 requests>=2.27.1 @@ -17,3 +18,4 @@ xarray==2023.1.0 xstac==1.1.0 zarr==2.13.6 boto3==1.24.59 +uvicorn==0.29.0 diff --git a/workflows_api/runtime/src/auth.py b/workflows_api/runtime/src/auth.py index 15b9b1eb..d902b69d 100644 --- a/workflows_api/runtime/src/auth.py +++ b/workflows_api/runtime/src/auth.py @@ -1,15 +1,23 @@ import logging -import requests +import jwt import src.config as config -from authlib.jose import JsonWebKey, JsonWebToken, JWTClaims, KeySet, errors -from cachetools import TTLCache, cached -from fastapi import Depends, HTTPException, security +from fastapi import Depends, HTTPException, Security, security, status +from typing import Annotated, Any, Dict + logger = logging.getLogger(__name__) -token_scheme = security.HTTPBearer() +settings = config.Settings() + +oauth2_scheme = security.OAuth2AuthorizationCodeBearer( + authorizationUrl=settings.cognito_authorization_url, + tokenUrl=settings.cognito_token_url, + refreshUrl=settings.cognito_token_url, +) + +jwks_client = jwt.PyJWKClient(settings.jwks_url) def get_settings() -> config.Settings: @@ -18,56 +26,37 @@ def get_settings() -> config.Settings: return main.settings -def get_jwks_url(settings: config.Settings = Depends(get_settings)) -> str: - import boto3 - import json - client = boto3.client("secretsmanager") - response = client.get_secret_value(SecretId=settings.workflows_client_secret_id) - secrets = json.loads(response["SecretString"]) - - return f"https://cognito-idp.{secrets['aws_region']}.amazonaws.com/{secrets['userpool_id']}/.well-known/jwks.json" - - -@cached(TTLCache(maxsize=1, ttl=3600)) -def get_jwks(jwks_url: str = Depends(get_jwks_url)) -> KeySet: - with requests.get(jwks_url) as response: - response.raise_for_status() - return JsonWebKey.import_key_set(response.json()) - - -def decode_token( - token: security.HTTPAuthorizationCredentials = Depends(token_scheme), - jwks: KeySet = Depends(get_jwks), -) -> JWTClaims: - """ - Validate & decode JWT - """ +def validated_token( + token_str: Annotated[str, Security(oauth2_scheme)], + required_scopes: security.SecurityScopes, +): + # Parse & validate token try: - claims = JsonWebToken(["RS256"]).decode( - s=token.credentials, - key=jwks, - claims_options={ - # # Example of validating audience to match expected value - # "aud": {"essential": True, "values": [APP_CLIENT_ID]} - }, + token = jwt.decode( + token_str, + jwks_client.get_signing_key_from_jwt(token_str).key, + algorithms=["RS256"], ) - - if "client_id" in claims: - # Insert Cognito's `client_id` into `aud` claim if `aud` claim is unset - claims.setdefault("aud", claims["client_id"]) - - claims.validate() - return claims - except errors.JoseError: # - logger.exception("Unable to decode token") - raise HTTPException(status_code=403, detail="Bad auth token") - - -def get_username(claims: security.HTTPBasicCredentials = Depends(decode_token)): - return claims["sub"] - - -def get_token( - token: security.HTTPAuthorizationCredentials = Depends(token_scheme), -): - return token.credentials + 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 + + +def get_username(token: Annotated[Dict[Any, Any], Depends(validated_token)]): + return token["username"] diff --git a/workflows_api/runtime/src/config.py b/workflows_api/runtime/src/config.py index 05611da0..3e2b83aa 100644 --- a/workflows_api/runtime/src/config.py +++ b/workflows_api/runtime/src/config.py @@ -1,15 +1,25 @@ -from pydantic import BaseSettings, Field, constr +from pydantic import AnyHttpUrl, BaseSettings, Field, constr +from typing import Optional AwsArn = constr(regex=r"^arn:aws:iam::\d{12}:role/.+") AwsStepArn = constr(regex=r"^arn:aws:states:.+:\d{12}:stateMachine:.+") class Settings(BaseSettings): + cognito_domain: AnyHttpUrl = Field( + description="The base url of the Cognito domain for authorization and token urls" + ) + client_id: str = Field(description="The Cognito APP client ID") data_access_role_arn: AwsArn = Field( # type: ignore description="ARN of AWS Role used to validate access to S3 data" ) + jwks_url: Optional[AnyHttpUrl] = Field( + description="URL of JWKS, e.g. https://cognito-idp.{region}.amazonaws.com/{userpool_id}/.well-known/jwks.json" + ) - workflows_client_secret_id: str = Field(description="The Cognito APP Secret that contains cognito creds") + workflows_client_secret_id: str = Field( + description="The Cognito APP Secret that contains cognito creds" + ) stage: str = Field(description="API stage") workflow_root_path: str = Field(description="Root path of API") ingest_url: str = Field(description="URL of ingest API") @@ -17,5 +27,15 @@ class Settings(BaseSettings): stac_url: str = Field(description="URL of STAC API") mwaa_env: str = Field(description="MWAA URL") + @property + def cognito_authorization_url(self) -> AnyHttpUrl: + """Cognito user pool authorization url""" + return f"{self.cognito_domain}/oauth2/authorize" + + @property + def cognito_token_url(self) -> AnyHttpUrl: + """Cognito user pool token and refresh url""" + return f"{self.cognito_domain}/oauth2/token" + class Config: env_file = ".env" diff --git a/workflows_api/runtime/src/main.py b/workflows_api/runtime/src/main.py index 5b04a49a..75af2a68 100644 --- a/workflows_api/runtime/src/main.py +++ b/workflows_api/runtime/src/main.py @@ -31,6 +31,11 @@ root_path=settings.workflow_root_path, openapi_url="/openapi.json", docs_url="/docs", + swagger_ui_init_oauth={ + "appName": "Cognito", + "clientId": settings.client_id, + "usePkceWithAuthorizationCodeGrant": True, + }, ) @@ -40,7 +45,7 @@ @workflows_app.post( "/dataset/validate", tags=["Dataset"], - dependencies=[Depends(auth.get_username)], + dependencies=[Depends(auth.validated_token)], ) def validate_dataset(dataset: schemas.COGDataset): # for all sample files in dataset, test access using raster /validate endpoint @@ -67,7 +72,7 @@ def validate_dataset(dataset: schemas.COGDataset): "/dataset/publish", tags=["Dataset"], dependencies=[Depends(auth.get_username)] ) async def publish_dataset( - token=Depends(auth.get_token), + token=Depends(auth.oauth2_scheme), dataset: Union[schemas.ZarrDataset, schemas.COGDataset] = Body( ..., discriminator="data_type" ), @@ -100,10 +105,12 @@ async def publish_dataset( response_model=schemas.WorkflowExecutionResponse, tags=["Workflow-Executions"], status_code=201, - dependencies=[Depends(auth.get_username)], + dependencies=[Depends(auth.validated_token)], ) async def start_discovery_workflow_execution( - input: Union[schemas.S3Input, schemas.CmrInput]=Body(..., discriminator="discovery"), + input: Union[schemas.S3Input, schemas.CmrInput] = Body( + ..., discriminator="discovery" + ), ) -> schemas.WorkflowExecutionResponse: """ Triggers the ingestion workflow @@ -115,7 +122,7 @@ async def start_discovery_workflow_execution( "/discovery-executions/{workflow_execution_id}", response_model=Union[schemas.ExecutionResponse, schemas.WorkflowExecutionResponse], tags=["Workflow-Executions"], - dependencies=[Depends(auth.get_username)], + dependencies=[Depends(auth.validated_token)], ) async def get_discovery_workflow_execution_status( workflow_execution_id: str, @@ -129,7 +136,7 @@ async def get_discovery_workflow_execution_status( @workflows_app.get( "/list-workflows", tags=["Workflow-Executions"], - dependencies=[Depends(auth.get_username)], + dependencies=[Depends(auth.validated_token)], ) async def get_workflow_list() -> ( Union[schemas.ExecutionResponse, schemas.WorkflowExecutionResponse] @@ -143,7 +150,7 @@ async def get_workflow_list() -> ( @workflows_app.post( "/cli-input", tags=["Admin"], - dependencies=[Depends(auth.get_username)], + dependencies=[Depends(auth.validated_token)], ) async def send_cli_command(cli_command: str): return airflow_helpers.send_cli_command(cli_command) @@ -153,3 +160,12 @@ async def send_cli_command(cli_command: str): @workflows_app.exception_handler(RequestValidationError) async def validation_exception_handler(request, exc): return JSONResponse(str(exc), status_code=422) + + +@workflows_app.get("/auth/me", tags=["Auth"]) +def who_am_i(claims=Depends(auth.validated_token)): + """ + Return claims for the provided JWT + """ + print(f"\n CLAIMS {claims}") + return claims diff --git a/workflows_api/runtime/src/validators.py b/workflows_api/runtime/src/validators.py index 1541a327..511c2c03 100644 --- a/workflows_api/runtime/src/validators.py +++ b/workflows_api/runtime/src/validators.py @@ -92,6 +92,7 @@ def collection_exists(collection_id: str) -> bool: f"{response.status_code} response code from STAC API" ) + def time_density_is_valid(is_periodic: bool, time_density: Union[str, None]): """ Ensures that the time_density is valid based on the value of is_periodic @@ -101,4 +102,4 @@ def time_density_is_valid(is_periodic: bool, time_density: Union[str, None]): # Literal[str, None] doesn't quite work for null field inputs from a dict() if time_density and time_density not in ["day", "month", "year"]: - raise ValueError("If set, time_density must be one of 'day, 'month' or 'year'") \ No newline at end of file + raise ValueError("If set, time_density must be one of 'day, 'month' or 'year'") From b237bd7c6d25c8fa3de98a4cfd073c4bd6d6602d Mon Sep 17 00:00:00 2001 From: Jennifer Tran Date: Thu, 25 Apr 2024 13:28:49 -0700 Subject: [PATCH 2/4] fix: revert .terraform.lock.hcl changes --- infrastructure/.terraform.lock.hcl | 82 ++++++++++++++++++------------ 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/infrastructure/.terraform.lock.hcl b/infrastructure/.terraform.lock.hcl index 30fdcc63..66df49bd 100644 --- a/infrastructure/.terraform.lock.hcl +++ b/infrastructure/.terraform.lock.hcl @@ -2,46 +2,44 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/archive" { - version = "2.4.0" + version = "2.3.0" hashes = [ - "h1:ZtsrX5F13Ohsv/k/BvgyBVn0gF+lW4bkG7JmCGrN35Y=", - "h1:cJokkjeH1jfpG4QEHdRx0t2j8rr52H33A7C/oX73Ok4=", - "zh:18e408596dd53048f7fc8229098d0e3ad940b92036a24287eff63e2caec72594", - "zh:392d4216ecd1a1fd933d23f4486b642a8480f934c13e2cae3c13b6b6a7e34a7b", - "zh:655dd1fa5ca753a4ace21d0de3792d96fff429445717f2ce31c125d19c38f3ff", - "zh:70dae36c176aa2b258331ad366a471176417a94dd3b4985a911b8be9ff842b00", + "h1:NaDbOqAcA9d8DiAS5/6+5smXwN3/+twJGb3QRiz6pNw=", + "zh:0869128d13abe12b297b0cd13b8767f10d6bf047f5afc4215615aabc39c2eb4f", + "zh:481ed837d63ba3aa45dd8736da83e911e3509dee0e7961bf5c00ed2644f807b3", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7d8c8e3925f1e21daf73f85983894fbe8868e326910e6df3720265bc657b9c9c", - "zh:a032ec0f0aee27a789726e348e8ad20778c3a1c9190ef25e7cff602c8d175f44", - "zh:b8e50de62ba185745b0fe9713755079ad0e9f7ac8638d204de6762cc36870410", - "zh:c8ad0c7697a3d444df21ff97f3473a8604c8639be64afe3f31b8ec7ad7571e18", - "zh:df736c5a2a7c3a82c5493665f659437a22f0baf8c2d157e45f4dd7ca40e739fc", - "zh:e8ffbf578a0977074f6d08aa8734e36c726e53dc79894cfc4f25fadc4f45f1df", - "zh:efea57ff23b141551f92b2699024d356c7ffd1a4ad62931da7ed7a386aef7f1f", + "zh:9f08fe2977e2166849be24fb9f394e4d2697414d463f7996fd0d7beb4e19a29c", + "zh:9fe566deeafd460d27999ca0bbfd85426a5fcfcb40007b23884deb76da127b6f", + "zh:a1bd9a60925d9769e0da322e4523330ee86af9dc2e770cba1d0247a999ef29cb", + "zh:bb4094c8149f74308b22a87e1ac19bcccca76e8ef021b571074d9bccf1c0c6f0", + "zh:c8984c9def239041ce41ec8e19bbd76a49e74ed2024ff736dad60429dee89bcc", + "zh:ea4bb5ae73db1de3a586e62f39106f5e56770804a55aa5e6b4f642df973e0e75", + "zh:f44a9d596ecc3a8c5653f56ba0cd202ad93b49f76767f4608daf7260b813289e", + "zh:f5c5e6cc9f7f070020ab7d95fcc9ed8e20d5cf219978295a71236e22cbb6d508", + "zh:fd2273f51dcc8f43403bf1e425ba9db08a57c3ddcba5ad7a51742ccde21ca611", ] } provider "registry.terraform.io/hashicorp/aws" { - version = "4.67.0" + version = "4.58.0" constraints = "~> 4.0, >= 4.54.0" hashes = [ - "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", - "h1:P43vwcDPG99x5WBbmqwUPgfJrfXf6/ucAIbGlRb7k1w=", - "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", - "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", - "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", - "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", - "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", - "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", - "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", - "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", - "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", + "h1:YIRXIr1ji0HLWLU0ae+UbUNOHc9MJaLrMHxH3LIQ/Vk=", + "zh:14b2b2dfbc7ee705c412d762b1485ee08958c816a64ac74f5769e946e4a1d265", + "zh:17a37e6825e2023b18987d31c0cbb9336654ea146b68e6c90710ea4636af71ae", + "zh:273127c69fb244577e5c136c46164d34f77b0c956c18d27f63d1072dd558f924", + "zh:4b2b6416d34fb3e1051c99d2a84045b136976140e34381d5fbf90e32db15272e", + "zh:7e6a8571ff15d51f892776265642ee01004b8553fd4f6f2014b6f3f2834670c7", + "zh:847c76ab2381b66666d0f79cf1ac697b5bfd0d9c3009fd11bc6ad6545d1eb427", + "zh:9a52cae08ba8d27d0639a8d2b8c61591027883058bf0cc5a639cffe1e299f019", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", - "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", - "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", - "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", - "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", + "zh:9df647e8322d6f94f1843366ba39d21c4b36c8e7dcdc03711d52e27f73b0e974", + "zh:9e52037e68409802ff913b166c30e3f2035af03865cbef0c1b03762bce853941", + "zh:a30288e7c3c904d6998d1709835d7c5800a739f8608f0837f960286a2b8b6e59", + "zh:a7f24e3bda3be566468e4ad62cef1016f68c6f5a94d2e3e979485bc05626281b", + "zh:ba326ba80f5e39829b67a6d1ce54ba52b171e5e13a0a91ef5f9170a9b0cc9ce4", + "zh:c4e3fe9f2be6e244a3dfce599f4b0be9e8fffaece64cbc65f3195f825f65489b", + "zh:f20a251af37039bb2c7612dbd2c5df3a25886b4cc78f902385a2850ea6e30d08", ] } @@ -49,7 +47,6 @@ provider "registry.terraform.io/hashicorp/external" { version = "2.3.1" constraints = ">= 1.0.0" hashes = [ - "h1:9rJggijNdRdFk//ViQPGZdK0xu9XU/9qBDijNsZJMg0=", "h1:gznGscVJ0USxy4CdihpjRKPsKvyGr/zqPvBoFLJTQDc=", "zh:001e2886dc81fc98cf17cf34c0d53cb2dae1e869464792576e11b0f34ee92f54", "zh:2eeac58dd75b1abdf91945ac4284c9ccb2bfb17fa9bdb5f5d408148ff553b3ee", @@ -70,7 +67,6 @@ provider "registry.terraform.io/hashicorp/local" { version = "2.4.0" constraints = ">= 1.0.0" hashes = [ - "h1:Bs7LAkV/iQTLv72j+cTMrvx2U3KyXrcVHaGbdns1NcE=", "h1:ZUEYUmm2t4vxwzxy1BvN1wL6SDWrDxfH7pxtzX8c6d0=", "zh:53604cd29cb92538668fe09565c739358dc53ca56f9f11312b9d7de81e48fab9", "zh:66a46e9c508716a1c98efbf793092f03d50049fa4a83cd6b2251e9a06aca2acf", @@ -107,3 +103,23 @@ provider "registry.terraform.io/hashicorp/null" { "zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694", ] } + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + constraints = ">= 2.2.0" + hashes = [ + "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} From fb3e1777a31e3d5aa02954a81f61102712feb83c Mon Sep 17 00:00:00 2001 From: Jennifer Tran Date: Thu, 25 Apr 2024 13:31:01 -0700 Subject: [PATCH 3/4] fix: revert .terraform.lock.hcl changes --- infrastructure/.terraform.lock.hcl | 79 +++++++++++------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/infrastructure/.terraform.lock.hcl b/infrastructure/.terraform.lock.hcl index 66df49bd..61c6933c 100644 --- a/infrastructure/.terraform.lock.hcl +++ b/infrastructure/.terraform.lock.hcl @@ -2,44 +2,44 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/archive" { - version = "2.3.0" + version = "2.4.0" hashes = [ - "h1:NaDbOqAcA9d8DiAS5/6+5smXwN3/+twJGb3QRiz6pNw=", - "zh:0869128d13abe12b297b0cd13b8767f10d6bf047f5afc4215615aabc39c2eb4f", - "zh:481ed837d63ba3aa45dd8736da83e911e3509dee0e7961bf5c00ed2644f807b3", + "h1:cJokkjeH1jfpG4QEHdRx0t2j8rr52H33A7C/oX73Ok4=", + "zh:18e408596dd53048f7fc8229098d0e3ad940b92036a24287eff63e2caec72594", + "zh:392d4216ecd1a1fd933d23f4486b642a8480f934c13e2cae3c13b6b6a7e34a7b", + "zh:655dd1fa5ca753a4ace21d0de3792d96fff429445717f2ce31c125d19c38f3ff", + "zh:70dae36c176aa2b258331ad366a471176417a94dd3b4985a911b8be9ff842b00", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:9f08fe2977e2166849be24fb9f394e4d2697414d463f7996fd0d7beb4e19a29c", - "zh:9fe566deeafd460d27999ca0bbfd85426a5fcfcb40007b23884deb76da127b6f", - "zh:a1bd9a60925d9769e0da322e4523330ee86af9dc2e770cba1d0247a999ef29cb", - "zh:bb4094c8149f74308b22a87e1ac19bcccca76e8ef021b571074d9bccf1c0c6f0", - "zh:c8984c9def239041ce41ec8e19bbd76a49e74ed2024ff736dad60429dee89bcc", - "zh:ea4bb5ae73db1de3a586e62f39106f5e56770804a55aa5e6b4f642df973e0e75", - "zh:f44a9d596ecc3a8c5653f56ba0cd202ad93b49f76767f4608daf7260b813289e", - "zh:f5c5e6cc9f7f070020ab7d95fcc9ed8e20d5cf219978295a71236e22cbb6d508", - "zh:fd2273f51dcc8f43403bf1e425ba9db08a57c3ddcba5ad7a51742ccde21ca611", + "zh:7d8c8e3925f1e21daf73f85983894fbe8868e326910e6df3720265bc657b9c9c", + "zh:a032ec0f0aee27a789726e348e8ad20778c3a1c9190ef25e7cff602c8d175f44", + "zh:b8e50de62ba185745b0fe9713755079ad0e9f7ac8638d204de6762cc36870410", + "zh:c8ad0c7697a3d444df21ff97f3473a8604c8639be64afe3f31b8ec7ad7571e18", + "zh:df736c5a2a7c3a82c5493665f659437a22f0baf8c2d157e45f4dd7ca40e739fc", + "zh:e8ffbf578a0977074f6d08aa8734e36c726e53dc79894cfc4f25fadc4f45f1df", + "zh:efea57ff23b141551f92b2699024d356c7ffd1a4ad62931da7ed7a386aef7f1f", ] } provider "registry.terraform.io/hashicorp/aws" { - version = "4.58.0" + version = "4.67.0" constraints = "~> 4.0, >= 4.54.0" hashes = [ - "h1:YIRXIr1ji0HLWLU0ae+UbUNOHc9MJaLrMHxH3LIQ/Vk=", - "zh:14b2b2dfbc7ee705c412d762b1485ee08958c816a64ac74f5769e946e4a1d265", - "zh:17a37e6825e2023b18987d31c0cbb9336654ea146b68e6c90710ea4636af71ae", - "zh:273127c69fb244577e5c136c46164d34f77b0c956c18d27f63d1072dd558f924", - "zh:4b2b6416d34fb3e1051c99d2a84045b136976140e34381d5fbf90e32db15272e", - "zh:7e6a8571ff15d51f892776265642ee01004b8553fd4f6f2014b6f3f2834670c7", - "zh:847c76ab2381b66666d0f79cf1ac697b5bfd0d9c3009fd11bc6ad6545d1eb427", - "zh:9a52cae08ba8d27d0639a8d2b8c61591027883058bf0cc5a639cffe1e299f019", + "h1:5Zfo3GfRSWBaXs4TGQNOflr1XaYj6pRnVJLX5VAjFX4=", + "zh:0843017ecc24385f2b45f2c5fce79dc25b258e50d516877b3affee3bef34f060", + "zh:19876066cfa60de91834ec569a6448dab8c2518b8a71b5ca870b2444febddac6", + "zh:24995686b2ad88c1ffaa242e36eee791fc6070e6144f418048c4ce24d0ba5183", + "zh:4a002990b9f4d6d225d82cb2fb8805789ffef791999ee5d9cb1fef579aeff8f1", + "zh:559a2b5ace06b878c6de3ecf19b94fbae3512562f7a51e930674b16c2f606e29", + "zh:6a07da13b86b9753b95d4d8218f6dae874cf34699bca1470d6effbb4dee7f4b7", + "zh:768b3bfd126c3b77dc975c7c0e5db3207e4f9997cf41aa3385c63206242ba043", + "zh:7be5177e698d4b547083cc738b977742d70ed68487ce6f49ecd0c94dbf9d1362", + "zh:8b562a818915fb0d85959257095251a05c76f3467caa3ba95c583ba5fe043f9b", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9df647e8322d6f94f1843366ba39d21c4b36c8e7dcdc03711d52e27f73b0e974", - "zh:9e52037e68409802ff913b166c30e3f2035af03865cbef0c1b03762bce853941", - "zh:a30288e7c3c904d6998d1709835d7c5800a739f8608f0837f960286a2b8b6e59", - "zh:a7f24e3bda3be566468e4ad62cef1016f68c6f5a94d2e3e979485bc05626281b", - "zh:ba326ba80f5e39829b67a6d1ce54ba52b171e5e13a0a91ef5f9170a9b0cc9ce4", - "zh:c4e3fe9f2be6e244a3dfce599f4b0be9e8fffaece64cbc65f3195f825f65489b", - "zh:f20a251af37039bb2c7612dbd2c5df3a25886b4cc78f902385a2850ea6e30d08", + "zh:9c385d03a958b54e2afd5279cd8c7cbdd2d6ca5c7d6a333e61092331f38af7cf", + "zh:b3ca45f2821a89af417787df8289cb4314b273d29555ad3b2a5ab98bb4816b3b", + "zh:da3c317f1db2469615ab40aa6baba63b5643bae7110ff855277a1fb9d8eb4f2c", + "zh:dc6430622a8dc5cdab359a8704aec81d3825ea1d305bbb3bbd032b1c6adfae0c", + "zh:fac0d2ddeadf9ec53da87922f666e1e73a603a611c57bcbc4b86ac2821619b1d", ] } @@ -87,7 +87,6 @@ provider "registry.terraform.io/hashicorp/null" { version = "3.2.1" constraints = ">= 2.0.0" hashes = [ - "h1:tSj1mL6OQ8ILGqR2mDu7OYYYWf+hoir0pf9KAQ8IzO8=", "h1:ydA0/SNRVB1o95btfshvYsmxA+jZFRZcvKzZSB+4S1M=", "zh:58ed64389620cc7b82f01332e27723856422820cfd302e304b5f6c3436fb9840", "zh:62a5cc82c3b2ddef7ef3a6f2fedb7b9b3deff4ab7b414938b08e51d6e8be87cb", @@ -103,23 +102,3 @@ provider "registry.terraform.io/hashicorp/null" { "zh:fca01a623d90d0cad0843102f9b8b9fe0d3ff8244593bd817f126582b52dd694", ] } - -provider "registry.terraform.io/hashicorp/random" { - version = "3.5.1" - constraints = ">= 2.2.0" - hashes = [ - "h1:IL9mSatmwov+e0+++YX2V6uel+dV6bn+fC/cnGDK3Ck=", - "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", - "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", - "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", - "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", - "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", - "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", - "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", - "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", - "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", - "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", - ] -} From c4f9829bb06b7c047dee8f62cc58a58dd68c0f34 Mon Sep 17 00:00:00 2001 From: Jennifer Tran Date: Thu, 25 Apr 2024 17:51:32 -0700 Subject: [PATCH 4/4] feat: add types --- workflows_api/runtime/src/auth.py | 7 +++++-- workflows_api/runtime/src/main.py | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/workflows_api/runtime/src/auth.py b/workflows_api/runtime/src/auth.py index d902b69d..3cddd39a 100644 --- a/workflows_api/runtime/src/auth.py +++ b/workflows_api/runtime/src/auth.py @@ -29,7 +29,10 @@ def get_settings() -> config.Settings: def validated_token( token_str: Annotated[str, Security(oauth2_scheme)], required_scopes: security.SecurityScopes, -): +) -> Dict[str, Any]: + """ + Returns an access token payload, see: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html + """ # Parse & validate token try: token = jwt.decode( @@ -58,5 +61,5 @@ def validated_token( return token -def get_username(token: Annotated[Dict[Any, Any], Depends(validated_token)]): +def get_username(token: Annotated[Dict[Any, Any], Depends(validated_token)]) -> str: return token["username"] diff --git a/workflows_api/runtime/src/main.py b/workflows_api/runtime/src/main.py index d406e0c3..79f47b38 100644 --- a/workflows_api/runtime/src/main.py +++ b/workflows_api/runtime/src/main.py @@ -37,7 +37,7 @@ "clientId": settings.client_id, "usePkceWithAuthorizationCodeGrant": True, }, - router = APIRouter(route_class=LoggerRouteHandler) + router=APIRouter(route_class=LoggerRouteHandler), ) @@ -158,7 +158,6 @@ async def send_cli_command(cli_command: str): return airflow_helpers.send_cli_command(cli_command) - # If the correlation header is used in the UI, we can analyze traces that originate from a given user or client @workflows_app.middleware("http") async def add_correlation_id(request: Request, call_next): @@ -185,24 +184,25 @@ async def add_correlation_id(request: Request, call_next): logger.info("Request completed") return response + @workflows_app.get("/auth/me", tags=["Auth"]) def who_am_i(claims=Depends(auth.validated_token)): """ Return claims for the provided JWT """ - print(f"\n CLAIMS {claims}") return claims - + + # exception handling @workflows_app.exception_handler(RequestValidationError) async def validation_exception_handler(request, exc): metrics.add_metric(name="ValidationErrors", unit=MetricUnit.Count, value=1) return JSONResponse(str(exc), status_code=422) + @workflows_app.exception_handler(Exception) async def general_exception_handler(request, err): """Handle exceptions that aren't caught elsewhere""" metrics.add_metric(name="UnhandledExceptions", unit=MetricUnit.Count, value=1) logger.exception(f"Unhandled exception: {err}") return JSONResponse(status_code=500, content={"detail": "Internal Server Error"}) -