diff --git a/Makefile b/Makefile index 7d6ffe7..cdee7fa 100644 --- a/Makefile +++ b/Makefile @@ -20,3 +20,10 @@ refresh_all_requirements: @if [ $$(uname -s) == "Darwin" ]; then sleep 1; fi # this is require because Darwin HFS+ only has second-resolution for timestamps. @touch requirements.txt.in @$(MAKE) requirements.txt + +clean: + git clean -Xdf chalice daemons $(MODULES) + git clean -df {chalice,daemons/*}/{chalicelib,domovoilib,vendor} + git checkout $$(git status --porcelain {chalice,daemons/*}/.chalice/config.json | awk '{print $$2}') + -rm -rf .*-env + -rm -rf node_modules diff --git a/chalice/.gitignore b/chalice/.gitignore new file mode 100644 index 0000000..877d3e3 --- /dev/null +++ b/chalice/.gitignore @@ -0,0 +1,14 @@ +# Reminder: +# - A leading slash means the pattern is anchored at the root. +# - No leading slash means the pattern matches at any depth. + +.chalice/deployed.json +.chalice/deployments/ +.chalice/policy.json +.chalice/policy-*.json +.chalice/venv/ +.chalice/ +chalicelib +vendor +requirements.txt +auth/ \ No newline at end of file diff --git a/chalice/Makefile b/chalice/Makefile new file mode 100644 index 0000000..733a49f --- /dev/null +++ b/chalice/Makefile @@ -0,0 +1,8 @@ +include ../common.mk + +deploy: + git clean -df chalicelib vendor + cp -R ../auth chalicelib + cp -R ../requirements.txt . + ./build_deploy_config.sh + chalice deploy --no-autogen-policy --stage $(DSS_DEPLOYMENT_STAGE) --api-gateway-stage $(DSS_DEPLOYMENT_STAGE) diff --git a/chalice/app.py b/chalice/app.py new file mode 100644 index 0000000..900a51b --- /dev/null +++ b/chalice/app.py @@ -0,0 +1,46 @@ +import logging +import chalice +import os +import sys +import json + +pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), 'chalicelib')) # noqa +sys.path.insert(0, pkg_root) # noqa + +import auth +from auth import util + + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +stage = os.getenv('DSS_DEPLOYMENT_STAGE') +app = chalice.Chalice(app_name=f"auth0-proxy-{stage}") +app.log.setLevel(logging.DEBUG) + + +@app.route('/', methods=['GET']) +def index(): + logger.info(dict(status='OK')) + return chalice.Response(body='OK', + headers={"Content-Type": "text/plain"}, + status_code=200) + +@app.route('/internal/login') +def login(): + """DSS Auth Integration: used to initiate login functionality for swagger interaction""" + application_secret_file = os.environ["GOOGLE_APPLICATION_SECRETS"] + + with open(application_secret_file, 'r') as fh: + application_secrets = json.loads(fh.read()) + query_params = dict(audience=Config.get_audience(), + client_id=util._deep_get(application_secrets, ['installed', 'client_id']), + client_secrets=util._deep_get(application_secrets, ['installed', 'client_secrets']), + redirect_uri=f'https://{Config.get_api_domain_name()}/internal/cb', + response_type='code', + scope='openid email profile') + # URL builder here + auth_url = UrlBuilder(url=Config.get_authz_url()).set(path='authorize') + for k, v in query_params.items(): + auth_url.add_query(query_name=k, query_value=v) + return chalice.Response(status_code=302, body='', headers=dict(Location=str(auth_url))) diff --git a/chalice/build_deploy_config.sh b/chalice/build_deploy_config.sh new file mode 100755 index 0000000..a67f876 --- /dev/null +++ b/chalice/build_deploy_config.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ -z $DSS_DEPLOYMENT_STAGE ]]; then + echo 'Please run "source environment" in the data-store repo root directory before running this command' + exit 1 +fi + +export stage=$DSS_DEPLOYMENT_STAGE +export dss_secrets_store=$DSS_SECRETS_STORE +deployed_json="$(dirname $0)/.chalice/deployed.json" +config_json="$(dirname $0)/.chalice/config.json" +policy_json="$(dirname $0)/.chalice/policy.json" +stage_policy_json="$(dirname $0)/.chalice/policy-${stage}.json" +export app_name=$(cat "$config_json" | jq -r .app_name) +iam_policy_template="$(dirname $0)/../iam/policy-templates/auth-proxy-lambda.json" +export lambda_name="${CHALICE_APP_NAME}" +export chalice_version=$(chalice --version) +export account_id=$(aws sts get-caller-identity | jq -r .Account) + +cat "$config_json" | jq ".app_name=\"${CHALICE_APP_NAME}\" " | sponge "$config_json" +cat "$config_json" | jq ".stages.$stage.api_gateway_stage=env.stage" | sponge "$config_json" + +export lambda_arn=$(aws lambda list-functions | jq -r '.Functions[] | select(.FunctionName==env.lambda_name) | .FunctionArn') +if [[ -z $lambda_arn ]]; then + echo "Lambda function $lambda_name not found, resetting Chalice config" + rm -f "$deployed_json" +else + api_arn=$(aws lambda get-policy --function-name "$lambda_name" | jq -r .Policy | jq -r '.Statement[0].Condition.ArnLike["AWS:SourceArn"]') + export api_id=$(echo "$api_arn" | cut -d ':' -f 6 | cut -d '/' -f 1) + jq -n ".$stage.api_handler_name = env.lambda_name | \ + .$stage.api_handler_arn = env.lambda_arn | \ + .$stage.rest_api_id = env.api_id | \ + .$stage.region = env.AWS_DEFAULT_REGION | \ + .$stage.api_gateway_stage = env.stage | \ + .$stage.backend = \"api\" | \ + .$stage.chalice_version = env.chalice_version | \ + .$stage.lambda_functions = {}" > "$deployed_json" +fi + +export DEPLOY_ORIGIN="$(whoami)-$(hostname)-$(git describe --tags --always)-$(date -u +'%Y-%m-%d-%H-%M-%S').deploy" +cat "$config_json" | jq ".stages.$stage.tags.DSS_DEPLOY_ORIGIN=\"$DEPLOY_ORIGIN\" | \ + .stages.$stage.tags.Name=\"${DSS_INFRA_TAG_PROJECT}-${DSS_INFRA_TAG_STAGE}-${DSS_INFRA_TAG_SERVICE}\" | \ + .stages.$stage.tags.service=\"${DSS_INFRA_TAG_SERVICE}\" | \ + .stages.$stage.tags.project=\"$DSS_INFRA_TAG_PROJECT\" | \ + .stages.$stage.tags.owner=\"${DSS_INFRA_TAG_OWNER}\" | \ + .stages.$stage.tags.env=\"${DSS_INFRA_TAG_STAGE}\"" | sponge "$config_json" + + +for ENV_KEY in $EXPORT_ENV_VARS_TO_LAMBDA; do + export env_val=$(printenv $ENV_KEY) + cat "$config_json" | jq ".stages.$stage.environment_variables.$ENV_KEY=\"$env_val\"" | sponge "$config_json" +done + +if [[ ${CI:-} != true ]]; then + # IAM policies must be updated from an operators machine, this will not run on CI environments. + echo "Looking for IAM Role: $iam_role_arn" + if ! aws iam get-role --role-name $lambda_name; then + export trust_policy_path=${DSS_HOME}/iam/trust-relations/lambda.json + aws iam create-role --role-name $lambda_name --path / --assume-role-policy-document file://$trust_policy_path + echo "created IAM Role: $lambda_name, with trust policy from $trust_policy_path" + fi + echo "updating $iam_role_arn with $stage_policy_json" + aws iam put-role-policy --role-name $lambda_name --policy-name $lambda_name --policy-document file://$stage_policy_json +fi + + +cat "$iam_policy_template" | envsubst '$DSS_MON_SECRETS_STORE $account_id $stage' > "$policy_json" +cp "$policy_json" "$stage_policy_json" diff --git a/environment b/environment index 8729b2e..5c38a50 100644 --- a/environment +++ b/environment @@ -20,6 +20,12 @@ DSS_INFRA_TAG_PROJECT="${DSS_PLATFORM}-dss" DSS_INFRA_TAG_SERVICE="dss" DSS_INFRA_TAG_OWNER="team-redwood-group@ucsc.edu" + +# TODO refactor this domain to work with prod, there might not be the stage name. +# this is used to setup the auth domain +API_DOMAIN_NAME="auth.${DSS_DEPLOYMENT_STAGE}.ucsc-cgp-redwood.org" +CHALICE_APP_NAME="auth0-proxy-${DSS_DEPLOYMENT_STAGE}" + # dont use interpolation on the stage here, its going to change the claims that are set in the auth0_rule # Warning back slashes are sensitive here OIDC_AUDIENCE=https://dev.ucsc-cgp-redwood.org/ diff --git a/iam/policy-templates/auth-proxy-lambda.json b/iam/policy-templates/auth-proxy-lambda.json new file mode 100644 index 0000000..2979ccc --- /dev/null +++ b/iam/policy-templates/auth-proxy-lambda.json @@ -0,0 +1,47 @@ + +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "iam:ListRoles", + "Resource": "arn:aws:iam::$account_id:role/" + }, + { + "Effect": "Allow", + "Action": "logs:*", + "Resource": [ + "arn:aws:logs:*:$account_id:log-group:dss-*-*", + "arn:aws:logs:*:*:*" + ] + }, + { + "Action": [ + "lambda:*", + "es:*", + "sns:*", + "states:*" + ], + "Resource": [ + "arn:aws:lambda:*:$account_id:function:dss-*", + "arn:aws:states:*:$account_id:*:dss-*" + ], + "Effect": "Allow" + }, + { + "Effect": "Allow", + "Action": "secretsmanager:Get*", + "Resource": "arn:aws:secretsmanager:*:$account_id:secret:$DSS_SECRETS_STORE/*" + }, + { + "Effect": "Allow", + "Action": [ + "tag:GetTagKeys", + "tag:GetResources", + "tag:GetTagValues", + "cloudwatch:*" + ], + "Resource": "*" + } + ] +} diff --git a/iam/trust-relations/lambda.json b/iam/trust-relations/lambda.json new file mode 100644 index 0000000..e1a853c --- /dev/null +++ b/iam/trust-relations/lambda.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} \ No newline at end of file