diff --git a/cdk/apps/ica_credentials/.mypy.ini b/cdk/apps/ica_credentials/.mypy.ini index e7cde3e7..ff0cb69d 100644 --- a/cdk/apps/ica_credentials/.mypy.ini +++ b/cdk/apps/ica_credentials/.mypy.ini @@ -1,9 +1,6 @@ [mypy] strict = True -[mypy-aws_cdk.*] -ignore_missing_imports = True - [mypy-boto3.*] ignore_missing_imports = True diff --git a/cdk/apps/ica_credentials/Makefile b/cdk/apps/ica_credentials/Makefile new file mode 100644 index 00000000..ffce60de --- /dev/null +++ b/cdk/apps/ica_credentials/Makefile @@ -0,0 +1,62 @@ +EXECUTABLES = python3 uv npx +K := $(foreach exec,$(EXECUTABLES), $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH"))) + +# +# by default we do everything to set up for running python and check the source is good +# + +all: setup-python-cdk typecheck + +setup: setup-python-cdk + +setup-python-cdk: .venv/install.touch + +.PHONY: all setup setup-python-cdk + +# +# the actual targets that should be targeted for running the various interesting activities +# + +deploy-cdk-dev: setup-python-cdk typecheck + . .venv/bin/activate; npx --yes cdk deploy ica-credentials-dev-stack + +typecheck: + . .venv/bin/activate; mypy lambdas/notify_slack_lambda/app + . .venv/bin/activate; mypy lambdas/jwt_producer_lambda/app + . .venv/bin/activate; mypy cdk + +format: + . .venv/bin/activate; black . + +.PHONY: deploy-cdk undeploy-cdk typecheck format + +# +# clean up working folders +# + +clean: clean-python-cdk + +clean-python-cdk: + find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete + rm -rf .venv/ + rm -rf cdk.out/ + rm -rf .mypy_cache/ + +.PHONY: clean clean-python-cdk + +# actual rules that create files/folders and build things + +# track the existence of the virtual with the activate script - everything is essentially dependent on this +# NOTE: the python version chosen here should match that chosen for lambdas in the CDK! +.venv/bin/activate: + uv venv -p 3.12 + +requirements.txt: requirements.in + . .venv/bin/activate; uv pip compile requirements.in -o requirements.txt + +requirements-dev.txt: requirements-dev.in + . .venv/bin/activate; uv pip compile requirements-dev.in -o requirements-dev.txt + +# track the actual time of pip install with a touch file - so that it will re-trigger on changes to requirements +.venv/install.touch: .venv/bin/activate requirements.txt requirements-dev.txt + . .venv/bin/activate; uv pip install -r requirements.txt -r requirements-dev.txt && touch .venv/install.touch diff --git a/cdk/apps/ica_credentials/README.md b/cdk/apps/ica_credentials/README.md index 42f623c9..e7924cd0 100644 --- a/cdk/apps/ica_credentials/README.md +++ b/cdk/apps/ica_credentials/README.md @@ -1,6 +1,6 @@ # ICA Credentials -A stack for managing ICA credentials and the production of up to date JWTs. +A stack for managing ICA credentials and the production of up-to-date JWTs (as AWS secrets). ## Setup @@ -48,7 +48,7 @@ The trick is: - then apply the CDK changes If your CDK stack gets stuck in "Failed Rollback": -- manually change each secret to Disabled +- manually change each secret rotation to Disabled - go to the Cloud Formation and 'continue rollback' - manually change each secret again to Disabled (the rollback will have re-enabled them) - then apply the CDK changes @@ -56,13 +56,25 @@ If your CDK stack gets stuck in "Failed Rollback": ## Dev -### Create Python virtual environment and install the dependencies +A development system requires a working Python and Node. -```bash -python3.8 -m venv .venv -source .venv/bin/activate -# [Optional] Needed to upgrade dependencies and cleanup unused packages -pip install pip-tools==6.1.0 -./scripts/install-deps.sh -``` +`make` should be all that is required to do a setup and type check of the source code. + +To actually deploy to dev, use + +`make deploy-cdk-dev` + +whilst in a shell with AWS access keys for dev. The deployed stack in dev will +perform rotations but only message to Slack infrequently. There are various +settings in the code if you want to test more frequent Slack messaging. + +If you want to change the Python requirements, just edit the relevant `requirements.in` +file and then do a `make`. It will re-compile the actual `requirements.txt` (and maybe +also possibly update package versions). + +NOTE: currently the lambdas do *not* require any Python libraries +(other than AWS and urllib which are built in) so are +built very simply by the CDK (they do not have their own `requirements.txt`). This +might need to change - at which point the CDK build will need to be more +sophisticated. diff --git a/cdk/apps/ica_credentials/cdk.json b/cdk/apps/ica_credentials/cdk.json index f7134b39..d73547a0 100644 --- a/cdk/apps/ica_credentials/cdk.json +++ b/cdk/apps/ica_credentials/cdk.json @@ -1,3 +1,3 @@ { - "app": "python app.py" + "app": "python cdk/app.py" } diff --git a/cdk/apps/ica_credentials/app.py b/cdk/apps/ica_credentials/cdk/app.py similarity index 65% rename from cdk/apps/ica_credentials/app.py rename to cdk/apps/ica_credentials/cdk/app.py index 4f01811a..9ed89f5b 100644 --- a/cdk/apps/ica_credentials/app.py +++ b/cdk/apps/ica_credentials/cdk/app.py @@ -1,13 +1,10 @@ -import os - -from aws_cdk import core as cdk +from aws_cdk import App, Environment from deployment import IcaCredentialsDeployment -app = cdk.App() +app = App() CDK_APP_NAME = "ica-credentials" -CDK_APP_PYTHON_VERSION = "3.8" ICA_BASE_URL = "https://aps2.platform.illumina.com" ICAV2_BASE_URL = "https://ica.illumina.com" @@ -21,6 +18,7 @@ IcaCredentialsDeployment( app, f"{CDK_APP_NAME}-dev", + False, "dc8e6ba9-b744-437b-b070-4cf014694b3d", [ # development_workflows @@ -33,16 +31,14 @@ SLACK_WEBHOOK_SSM_NAME, github_repos=[CWL_ICA_GITHUB_REPO], github_role_name=f"{CDK_APP_NAME}-dev-umccr-pipelines-deployment-role", - env=cdk.Environment( - account=os.environ["CDK_DEFAULT_ACCOUNT"], - region=os.environ["CDK_DEFAULT_REGION"], - ), + env=Environment(account="843407916570", region="ap-southeast-2"), ) # Staging IcaCredentialsDeployment( app, f"{CDK_APP_NAME}-stg", + False, "c9173925-a838-4394-9fc6-61cb93c252a1", [ # staging_workflows - no staging workflows project @@ -52,16 +48,14 @@ SLACK_WEBHOOK_SSM_NAME, github_repos=[CWL_ICA_GITHUB_REPO], github_role_name=f"{CDK_APP_NAME}-stg-umccr-pipelines-deployment-role", - env=cdk.Environment( - account="455634345446", - region="ap-southeast-2" - ), + env=Environment(account="455634345446", region="ap-southeast-2"), ) # Production IcaCredentialsDeployment( app, f"{CDK_APP_NAME}-prod", + False, "20b42a71-1ebc-4e7b-b659-313f2f4524c3", [ # production_workflows @@ -72,25 +66,7 @@ SLACK_WEBHOOK_SSM_NAME, github_repos=[CWL_ICA_GITHUB_REPO], github_role_name=f"{CDK_APP_NAME}-prod-umccr-pipelines-deployment-role", - env=cdk.Environment( - account="472057503814", - region="ap-southeast-2" - ), -) - -# V2 (single token) -IcaCredentialsDeployment( - app, - f"{CDK_APP_NAME}-dev-v2", - None, # Token does not require project context in v2 - None, # Token does not require additional project list in v2 - ICAV2_BASE_URL, - SLACK_HOST_SSM_NAME, - SLACK_WEBHOOK_SSM_NAME, - env=cdk.Environment( - account=os.environ["CDK_DEFAULT_ACCOUNT"], - region=os.environ["CDK_DEFAULT_REGION"], - ), + env=Environment(account="472057503814", region="ap-southeast-2"), ) app.synth() diff --git a/cdk/apps/ica_credentials/deployment.py b/cdk/apps/ica_credentials/cdk/deployment.py similarity index 72% rename from cdk/apps/ica_credentials/deployment.py rename to cdk/apps/ica_credentials/cdk/deployment.py index f7bcad0c..8591224c 100644 --- a/cdk/apps/ica_credentials/deployment.py +++ b/cdk/apps/ica_credentials/cdk/deployment.py @@ -1,23 +1,25 @@ -from typing import Any, List, Optional, Dict +from typing import List, Optional, Any -from aws_cdk import core as cdk +from constructs import Construct +from aws_cdk import App, Stack, Stage from secrets.infrastructure import Secrets -class IcaCredentialsDeployment(cdk.Stage): +class IcaCredentialsDeployment(Stack): def __init__( self, - scope: cdk.Construct, + scope: Construct, id_: str, + v2_naming: bool, data_project: Optional[str], workflow_projects: Optional[List[str]], ica_base_url: str, slack_host_ssm_name: str, slack_webhook_ssm_name: str, github_role_name: Optional[str] = None, - github_repos: Optional[List] = None, - **kwargs, + github_repos: Optional[List[str]] = None, + **kwargs: Any, ): """ Represents the deployment of our stack(s) to a particular environment with a particular set of settings. @@ -25,6 +27,7 @@ def __init__( Args: scope: id_: + v2_naming: data_project: workflow_projects: ica_base_url: @@ -34,19 +37,19 @@ def __init__( """ super().__init__(scope, id_, **kwargs) - stateful = cdk.Stack(self, "stack") + # stateful = Stack(self, "stack") # this name becomes the prefix of our secrets so we slip in the word ICA to make it # obvious when someone sees them that they are associated with ICA Secrets( - stateful, - "IcaSecrets", + self, + "IcaV2Secrets" if v2_naming else "IcaSecrets", data_project, workflow_projects, ica_base_url, + "cron(0 4/12 * * ? *)", slack_host_ssm_name, slack_webhook_ssm_name, github_role_name=github_role_name, github_repos=github_repos, - cdk_env=kwargs.get("env") ) diff --git a/cdk/apps/ica_credentials/secrets/__init__.py b/cdk/apps/ica_credentials/cdk/secrets/__init__.py similarity index 100% rename from cdk/apps/ica_credentials/secrets/__init__.py rename to cdk/apps/ica_credentials/cdk/secrets/__init__.py diff --git a/cdk/apps/ica_credentials/secrets/infrastructure.py b/cdk/apps/ica_credentials/cdk/secrets/infrastructure.py similarity index 67% rename from cdk/apps/ica_credentials/secrets/infrastructure.py rename to cdk/apps/ica_credentials/cdk/secrets/infrastructure.py index 9f78eb01..3ade480c 100644 --- a/cdk/apps/ica_credentials/secrets/infrastructure.py +++ b/cdk/apps/ica_credentials/cdk/secrets/infrastructure.py @@ -1,48 +1,60 @@ import os -from typing import List, Tuple, Union, Optional +from typing import List, Tuple, Union, Optional, Mapping, Dict, cast + +from constructs import Construct +from aws_cdk import App, Environment, Duration, Stack, Fn from aws_cdk import ( - core as cdk, aws_lambda as lambda_, - aws_iam as iam, aws_secretsmanager as secretsmanager, + aws_events as events, + aws_events_targets as events_targets, + aws_iam as iam, ) -from aws_cdk.aws_events import Rule -from aws_cdk.aws_events_targets import LambdaFunction -from aws_cdk.aws_iam import PolicyStatement, Role, FederatedPrincipal -from aws_cdk.core import Duration import logging ROTATION_DAYS = 1 -class Secrets(cdk.Construct): +class Secrets(Construct): """ A construct that maintains secrets for ICA and periodically generates fresh JWT secrets. + + Args: + scope: + id_: + data_project: + workflow_projects: + ica_base_url: + rotation_cron: + slack_host_ssm_name: + slack_webhook_ssm_name: + github_repos: + github_role_name: """ def __init__( - self, - scope: cdk.Construct, - id_: str, - data_project: Optional[str], - workflow_projects: Optional[List[str]], - ica_base_url: str, - slack_host_ssm_name: str, - slack_webhook_ssm_name: str, - github_repos: Optional[List[str]], - github_role_name: str, - cdk_env: cdk.Environment + self, + scope: Construct, + id_: str, + data_project: Optional[str], + workflow_projects: Optional[List[str]], + ica_base_url: str, + rotation_cron: str, + slack_host_ssm_name: str, + slack_webhook_ssm_name: str, + github_repos: Optional[List[str]], + github_role_name: Optional[str], ): super().__init__(scope, id_) master = self.create_master_secret() jwt_portal_secret, jwt_portal_func = self.create_jwt_secret( - master, ica_base_url, id_ + "Portal", data_project + master, ica_base_url, id_ + "Portal", rotation_cron, data_project ) jwt_workflow_secret, jwt_workflow_func = self.create_jwt_secret( - master, ica_base_url, id_ + "Workflow", workflow_projects + master, ica_base_url, id_ + "Workflow", rotation_cron, workflow_projects ) # set the policy for the master secret to deny everyone except the rotators access @@ -55,13 +67,13 @@ def __init__( slack_webhook_ssm_name, ) - # share the JWT secrets with the GitHub Actions repo - self.share_jwt_secret_with_github_actions_repo( - jwt_workflow_secret, - github_repos, - github_role_name, - cdk_env.account - ) + # share the JWT secrets with the GitHub Actions repo if asked + if github_repos and github_role_name: + self.share_jwt_secret_with_github_actions_repo( + jwt_workflow_secret, + github_repos, + github_role_name, + ) def create_master_secret(self) -> secretsmanager.Secret: """ @@ -83,9 +95,9 @@ def create_master_secret(self) -> secretsmanager.Secret: return master_secret def add_deny_for_everyone_except( - self, - master_secret: secretsmanager.Secret, - producer_functions: List[lambda_.Function], + self, + master_secret: secretsmanager.Secret, + producer_functions: List[lambda_.Function], ) -> None: """ Sets up the master secret resource policy so that everything *except* the given functions @@ -123,11 +135,12 @@ def add_deny_for_everyone_except( ) def create_jwt_secret( - self, - master_secret: secretsmanager.Secret, - ica_base_url: str, - key_name: str, - project_ids: Optional[Union[str, List[str]]], + self, + master_secret: secretsmanager.Secret, + ica_base_url: str, + key_name: str, + rotation_cron: str, + project_ids: Optional[Union[str, List[str]]], ) -> Tuple[secretsmanager.Secret, lambda_.Function]: """ Create a JWT holding secret - that will use the master secret for JWT making - and which will have @@ -137,6 +150,7 @@ def create_jwt_secret( master_secret: the master secret to read for the API key for JWT making ica_base_url: the base url of ICA to be passed on to the rotators key_name: a unique string that we use to name this JWT secret + rotation_cron: a cron expression for the rotation times of the secret (must follow Secrets cron rules) project_ids: *either* a single string or a list of string - the choice of type *will* affect the resulting secret output i.e a string input will end up different to a list with one string! @@ -144,9 +158,9 @@ def create_jwt_secret( the JWT secret """ dirname = os.path.dirname(__file__) - filename = os.path.join(dirname, "runtime/jwt_producer") + filename = os.path.join(dirname, "../../lambdas/jwt_producer_lambda") - env = { + env: dict[str, str] = { "MASTER_ARN": master_secret.secret_arn, "ICA_BASE_URL": ica_base_url, } @@ -157,6 +171,9 @@ def create_jwt_secret( env["ICA_PLATFORM_VERSION"] = "V2" else: # V1 env["ICA_PLATFORM_VERSION"] = "V1" + if project_ids is None: + raise Exception("No project_ids provided for a V1 rotator") + if isinstance(project_ids, List): env["PROJECT_IDS"] = " ".join(project_ids) else: @@ -165,9 +182,10 @@ def create_jwt_secret( jwt_producer = lambda_.Function( self, "JwtProduce" + key_name, - runtime=lambda_.Runtime.PYTHON_3_8, + # yes this is a "got ()->Runtime" warning in PyCharm and no - it is not correct + runtime=lambda_.Runtime.PYTHON_3_12, code=lambda_.AssetCode(filename), - handler="lambda_entrypoint.main", + handler="app.lambda_entrypoint.main", timeout=Duration.minutes(1), environment=env, ) @@ -184,20 +202,31 @@ def create_jwt_secret( description="JWT(s) providing access to ICA projects", ) - # the rotation function that creates JWTs - jwt_secret.add_rotation_schedule( + # we need to drop into L1 construct world in order to specify rotations using cron expressions + # (this should be replaced as soon as the L2 constructs have Cron) + + sched = jwt_secret.add_rotation_schedule( "JwtSecretRotation", - automatically_after=Duration.days(ROTATION_DAYS), rotation_lambda=jwt_producer, + # this duration will be replaced below with the actual desired cron expression + automatically_after=Duration.hours(4), ) + if not sched.node.default_child: + raise Exception("The assumption of our use of L1 CDK is that there is a default child") + + # make direct property changes to support cron + sched_cfn = cast(secretsmanager.CfnRotationSchedule, sched.node.default_child) + sched_cfn.add_property_override("RotationRules.ScheduleExpression", rotation_cron) + sched_cfn.add_property_deletion_override("RotationRules.AutomaticallyAfterDays") + return jwt_secret, jwt_producer def create_event_handling( - self, - secrets: List[secretsmanager.Secret], - slack_host_ssm_name: str, - slack_webhook_ssm_name: str, + self, + secrets: List[secretsmanager.Secret], + slack_host_ssm_name: str, + slack_webhook_ssm_name: str, ) -> lambda_.Function: """ @@ -210,7 +239,7 @@ def create_event_handling( a lambda event handler """ dirname = os.path.dirname(__file__) - filename = os.path.join(dirname, "runtime/notify_slack") + filename = os.path.join(dirname, "../../lambdas/notify_slack_lambda") env = { # for the moment we don't parametrise at the CDK level.. only needed if this is liable to change @@ -221,31 +250,36 @@ def create_event_handling( notifier = lambda_.Function( self, "NotifySlack", - runtime=lambda_.Runtime.PYTHON_3_8, + # yes this is a "got ()->Runtime" warning in PyCharm and no - it is not correct + runtime=lambda_.Runtime.PYTHON_3_12, code=lambda_.AssetCode(filename), - handler="lambda_entrypoint.main", + handler="app.lambda_entrypoint.main", timeout=Duration.minutes(1), environment=env, ) - get_ssm_policy = PolicyStatement() + get_ssm_policy = iam.PolicyStatement() - # there is some weirdness around SSM parameter ARN formation and leading slashes.. can't be bothered + # there is some weirdness around SSM parameter ARN formation and leading slashes... can't be bothered # looking into right now - as the ones we want to use do a have a leading slash # but put in this exception in case - if not slack_webhook_ssm_name.startswith("/") or not slack_host_ssm_name.startswith("/"): + if not slack_webhook_ssm_name.startswith( + "/" + ) or not slack_host_ssm_name.startswith("/"): raise Exception("SSM parameters need to start with a leading slash") # see here - the *required* slash between parameter and the actual name uses the leading slash from the actual - # name itself.. which is wrong.. + # name itself... which is wrong... get_ssm_policy.add_resources(f"arn:aws:ssm:*:*:parameter{slack_host_ssm_name}") - get_ssm_policy.add_resources(f"arn:aws:ssm:*:*:parameter{slack_webhook_ssm_name}") + get_ssm_policy.add_resources( + f"arn:aws:ssm:*:*:parameter{slack_webhook_ssm_name}" + ) get_ssm_policy.add_actions("ssm:GetParameter") notifier.add_to_role_policy(get_ssm_policy) # we want a rule that traps all the rotation failures for our JWT secrets - rule = Rule( + rule = events.Rule( self, "NotifySlackRule", ) @@ -261,23 +295,22 @@ def create_event_handling( }, ) - rule.add_target(LambdaFunction(notifier)) + rule.add_target(events_targets.LambdaFunction(notifier)) return notifier def share_jwt_secret_with_github_actions_repo( - self, - secret: secretsmanager.Secret, - github_repositories: Optional[List[str]], - role_name: Optional[str], - account_id: Optional[str] - ): + self, + secret: secretsmanager.Secret, + github_repositories: List[str], + role_name: str, + ) -> None: """ - Given a list of GitHub repositories, allow this secret to be accessed by the repo + Given a list of GitHub repositories, allow this secret to be accessed by the repos. + :param secret: The secretsmanager object that will shared the role will have access to the value of :param github_repositories: A list of GitHub repositories that will be given access to the secret :param role_name: The name of the role that will be created and given access to the secret - :param account_id: The AWS account ID of the account that the role will be created in :return: """ # Check inputs @@ -290,31 +323,31 @@ def share_jwt_secret_with_github_actions_repo( return # Set role - gh_action_role = Role( + gh_action_role = iam.Role( self, role_name, - assumed_by=FederatedPrincipal( - f"arn:aws:iam::{account_id}:oidc-provider/token.actions.githubusercontent.com", + assumed_by=iam.FederatedPrincipal( + "arn:aws:iam::" + + Stack.of(self).account + + ":oidc-provider/token.actions.githubusercontent.com", { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": github_repositories - } + }, }, - "sts:AssumeRoleWithWebIdentity" - ) + "sts:AssumeRoleWithWebIdentity", + ), ) # Add permissions to role gh_action_role.add_to_policy( - PolicyStatement( + iam.PolicyStatement( actions=[ "secretsmanager:GetSecretValue", ], - resources=[ - secret.secret_arn - ] + resources=[secret.secret_arn], ) ) diff --git a/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/README.md b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/README.md new file mode 100644 index 00000000..069a9458 --- /dev/null +++ b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/README.md @@ -0,0 +1,2 @@ +Yes, we need an extra 'app' folder to hold the Python files. That makes relative +imports work. diff --git a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/__init__.py b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/__init__.py similarity index 100% rename from cdk/apps/ica_credentials/secrets/runtime/jwt_producer/__init__.py rename to cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/__init__.py diff --git a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/ica_common.py b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/ica_common.py similarity index 62% rename from cdk/apps/ica_credentials/secrets/runtime/jwt_producer/ica_common.py rename to cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/ica_common.py index 490307f9..3f70dfa7 100644 --- a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/ica_common.py +++ b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/ica_common.py @@ -7,32 +7,42 @@ # Two wrapper scripts for v1 and v2 platforms respectfully def api_key_to_jwt_for_project_v1(ica_base_url: str, api_key: str, cid: str) -> str: - return api_key_to_jwt_for_project(url=f"{ica_base_url}/v1/tokens", - accept_value="application/json", - encoded_params=urlencode({'cid': cid}), - api_key=api_key, - output_attribute="access_token") + return api_key_to_jwt_for_project( + url=f"{ica_base_url}/v1/tokens", + accept_value="application/json", + encoded_params=urlencode({"cid": cid}), + api_key=api_key, + output_attribute="access_token", + ) -def api_key_to_jwt_for_project_v2(ica_base_url: str, api_key: str) -> str: - return api_key_to_jwt_for_project(url=f"{ica_base_url}/ica/rest/api/tokens", - accept_value="application/vnd.illumina.v3+json", - encoded_params=None, - api_key=api_key, - output_attribute="token") +def api_key_to_jwt_for_project_v2(ica_base_url: str, api_key: str, cid: str) -> str: + return api_key_to_jwt_for_project( + url=f"{ica_base_url}/ica/rest/api/tokens", + accept_value="application/vnd.illumina.v3+json", + encoded_params=None, + api_key=api_key, + output_attribute="token", + ) -def api_key_to_jwt_for_project(url: str, accept_value: str, encoded_params: Optional[str], api_key: str, output_attribute: str) -> str: +def api_key_to_jwt_for_project( + url: str, + accept_value: str, + encoded_params: Optional[str], + api_key: str, + output_attribute: str, +) -> str: """ Using the API key, exchanges it for a JWT that will have the API keys user permission *only* in the passed in project context. Args: - ica_base_url: the base URL for ICA + url: the base URL for ICA accept_value: Either 'application/json' or 'application/vnd.illumina.v3+json' for v1 or v2 respectfully - api_key: the API key for a user encoded_params: For v1 projects, is a project ID required? Or a tenant required for v2? - cid: the project id, for v1 projects only. + api_key: the API key for a user + output_attribute: Returns: a JWT access token @@ -62,7 +72,7 @@ def api_key_to_jwt_for_project(url: str, accept_value: str, encoded_params: Opti if output_attribute in body_as_json: # success - return body_as_json[output_attribute] + return str(body_as_json[output_attribute]) # fall through to failure print(f"Failed ICA token exchange POST to '{url}'") diff --git a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/lambda_entrypoint.py b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/lambda_entrypoint.py similarity index 86% rename from cdk/apps/ica_credentials/secrets/runtime/jwt_producer/lambda_entrypoint.py rename to cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/lambda_entrypoint.py index fe019882..430a288d 100644 --- a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/lambda_entrypoint.py +++ b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/lambda_entrypoint.py @@ -5,7 +5,7 @@ import boto3 -from secret_manager_common import ( +from .secret_manager_common import ( get_master_api_key, do_finish_secret, do_create_secret, @@ -47,10 +47,14 @@ def main(ev: Any, _: Any) -> Any: project_ids = None if is_ica_v2_platform := os.environ["ICA_PLATFORM_VERSION"] == "V2": - from ica_common import api_key_to_jwt_for_project_v2 as api_key_to_jwt_for_project + from .ica_common import ( + api_key_to_jwt_for_project_v2 as api_key_to_jwt_for_project, + ) else: # V1 # We import the api_key_to_jwt_for_project method for v1 - from ica_common import api_key_to_jwt_for_project_v1 as api_key_to_jwt_for_project + from .ica_common import ( + api_key_to_jwt_for_project_v1 as api_key_to_jwt_for_project, + ) # we operate in two basic modes - in one we have a single project id and generate a single JWT for it # when given multiple ids however, ICA doesn't allow a combined JWT - so instead we generate a dictionary @@ -90,17 +94,22 @@ def main(ev: Any, _: Any) -> Any: def exchange_multi() -> str: result = {} - for p in project_ids: + for p in project_ids or []: result[p] = api_key_to_jwt_for_project(ica_base_url, master_val, p) return json.dumps(result) def exchange_single() -> str: - return api_key_to_jwt_for_project(ica_base_url, master_val, project_id) + return api_key_to_jwt_for_project(ica_base_url, master_val, str(project_id)) do_create_secret( - sm_client, arn, tok, - exchange_single if is_ica_v2_platform or project_id is not None - else exchange_multi + sm_client, + arn, + tok, + ( + exchange_single + if is_ica_v2_platform or project_id is not None + else exchange_multi + ), ) # JWTs are not immediately useful due to ICA clock skew and nbf claims diff --git a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/secret_manager_common.py b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/secret_manager_common.py similarity index 98% rename from cdk/apps/ica_credentials/secrets/runtime/jwt_producer/secret_manager_common.py rename to cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/secret_manager_common.py index 5a649001..e98dbf1f 100644 --- a/cdk/apps/ica_credentials/secrets/runtime/jwt_producer/secret_manager_common.py +++ b/cdk/apps/ica_credentials/lambdas/jwt_producer_lambda/app/secret_manager_common.py @@ -2,7 +2,7 @@ from typing import Any, Callable -def get_master_api_key(client: Any, master_arn) -> str: +def get_master_api_key(client: Any, master_arn: str) -> str: """ Get the master API key from the string content of the given secret arn. diff --git a/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/README.md b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/README.md new file mode 100644 index 00000000..069a9458 --- /dev/null +++ b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/README.md @@ -0,0 +1,2 @@ +Yes, we need an extra 'app' folder to hold the Python files. That makes relative +imports work. diff --git a/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/__init__.py b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/lambda_entrypoint.py b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/lambda_entrypoint.py new file mode 100644 index 00000000..5b962e46 --- /dev/null +++ b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/lambda_entrypoint.py @@ -0,0 +1,36 @@ +import os +from datetime import datetime, timezone +from typing import Any + +from .notify_slack import send_secrets_event_to_slack + + +def main(ev: Any, _: Any) -> Any: + """ + Send a notification to Slack for an event that occurred in Secrets Manager + """ + print(f"Starting slack notifier for Secrets Manager") + + slack_host_ssm_name = os.environ["SLACK_HOST_SSM_NAME"] + + if not slack_host_ssm_name: + raise Exception( + "SLACK_HOST_SSM_NAME must be specified in the Notify Slack lambda environment" + ) + + slack_webhook_ssm_name = os.environ["SLACK_WEBHOOK_SSM_NAME"] + + if not slack_webhook_ssm_name: + raise Exception( + "SLACK_WEBHOOK_SSM_NAME must be specified in the Notify Slack lambda environment" + ) + + # to avoid massive Slack spam - we chose to only actually display a message + # on limited days and time windows + n = datetime.now(timezone.utc) + + # only message in the first half of the day on the first day of the week (UTC!) + # obviously the rotation cron schedules must in some way match up so that this window is used + if n.isoweekday() == 1: + if n.hour < 12: + send_secrets_event_to_slack(ev, slack_host_ssm_name, slack_webhook_ssm_name) diff --git a/cdk/apps/ica_credentials/secrets/runtime/notify_slack/notify_slack.py b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/notify_slack.py similarity index 82% rename from cdk/apps/ica_credentials/secrets/runtime/notify_slack/notify_slack.py rename to cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/notify_slack.py index ecb2bd78..7b1c3d21 100644 --- a/cdk/apps/ica_credentials/secrets/runtime/notify_slack/notify_slack.py +++ b/cdk/apps/ica_credentials/lambdas/notify_slack_lambda/app/notify_slack.py @@ -1,27 +1,33 @@ -import os -import json +from json import dumps +from http.client import HTTPSConnection from typing import Any import boto3 -import http.client -def get_aws_account_name(id: str) -> str: - if id == "472057503814": +def get_aws_account_name(account_id: str) -> str: + """ + Get a printable AWS account name for our known accounts ids. + + Args: + account_id: + + Returns: + a printable AWS account name, or the account number + """ + if account_id == "472057503814": return "prod" - elif id == "455634345446": + elif account_id == "455634345446": return "stg" - elif id == "843407916570": + elif account_id == "843407916570": return "dev" - elif id == "620123204273": - return "dev (old)" - elif id == "602836945884": + elif account_id == "602836945884": return "agha" else: - return id + return account_id -def get_ssm_param_value(name: str) -> str: +def get_ssm_param_value(name: str) -> Any: """ Fetch the parameter with the given name from SSM Parameter Store. """ @@ -33,14 +39,14 @@ def get_ssm_param_value(name: str) -> str: def call_slack_webhook( - slack_host_ssm_name: str, slack_webhook_ssm_name: str, message: any -): + slack_host_ssm_name: str, slack_webhook_ssm_name: str, message: Any +) -> int: slack_host = get_ssm_param_value(slack_host_ssm_name) slack_webhook_endpoint = "/services/" + get_ssm_param_value(slack_webhook_ssm_name) - connection = http.client.HTTPSConnection(slack_host) + connection = HTTPSConnection(slack_host) - content = json.dumps(message) + content = dumps(message) print( f"Making HTTPS POST to '{slack_host}' and endpoint '{slack_webhook_endpoint}'" @@ -59,50 +65,12 @@ def call_slack_webhook( return response.status -def send_secrets_event_to_slack( - event: Any, slack_host_ssm_name: str, slack_webhook_ssm_name: str -) -> None: - """ - Print the details of a SecretsManager event to Slack. - - Args: - event: the event we are logging to Slack - slack_host_ssm_name: the SSM parameter name that holds the hostname of the Slack hook - slack_webhook_ssm_name: the SSM parameter name that holds the secret id for our webhook - """ - # Log the received event in CloudWatch - print(f"Received event: {json.dumps(event)}") - - # we expect events of a defined format so if not matching we must abort - if event.get("source") != "aws.secretsmanager": - raise ValueError("Unexpected event format!") - - print("Processing SecretsManager event...") - - try: - if event.get("detail-type") == "AWS Service Event via CloudTrail": - response = call_slack_webhook( - slack_host_ssm_name, - slack_webhook_ssm_name, - event_as_slack_message(event), - ) - - print(f"Response status: {response}") - - return - - print("Ended up not printing any Slack message") - - except Exception as e: - print(e) - - def event_as_slack_message(event: Any) -> Any: """ Convert the given AWS event for Secrets Manager into a message packet we can send to Slack. Args: - event: + event: a rotation event Returns: a dictionary representing a message that can be posted to Slack @@ -163,18 +131,56 @@ def event_as_slack_message(event: Any) -> Any: } if event_name == "RotationFailed": - msg[ - "text" - ] = f"❌ Periodic JWT key generation *failed* in `{aws_account_name}` for secret `{secret_id}`" + msg["text"] = ( + f"❌ Periodic JWT key generation *failed* in `{aws_account_name}` for secret `{secret_id}`" + ) elif event_name == "RotationSucceeded": - msg[ - "text" - ] = f"✅ Periodic JWT key generation *succeeded* in `{aws_account_name}` for secret `{secret_id}`" + msg["text"] = ( + f"✅ Periodic JWT key generation *succeeded* in `{aws_account_name}` for secret `{secret_id}`" + ) else: - msg[ - "text" - ] = f"? Unknown event named `{event_name}` in `{aws_account_name}` for secret `{secret_id}`" + msg["text"] = ( + f"? Unknown event named `{event_name}` in `{aws_account_name}` for secret `{secret_id}`" + ) return msg + + +def send_secrets_event_to_slack( + event: Any, slack_host_ssm_name: str, slack_webhook_ssm_name: str +) -> None: + """ + Print the details of a SecretsManager event to Slack. + + Args: + event: the event we are logging to Slack + slack_host_ssm_name: the SSM parameter name that holds the hostname of the Slack hook + slack_webhook_ssm_name: the SSM parameter name that holds the secret id for our webhook + """ + # Log the received event in CloudWatch + print(f"Received event: {dumps(event)}") + + # we expect events of a defined format so if not matching we must abort + if event.get("source") != "aws.secretsmanager": + raise ValueError("Unexpected event format!") + + print("Processing SecretsManager event...") + + try: + if event.get("detail-type") == "AWS Service Event via CloudTrail": + response = call_slack_webhook( + slack_host_ssm_name, + slack_webhook_ssm_name, + event_as_slack_message(event), + ) + + print(f"Response status: {response}") + + return + + print("Ended up not printing any Slack message") + + except Exception as e: + print(e) diff --git a/cdk/apps/ica_credentials/requirements-dev.txt b/cdk/apps/ica_credentials/requirements-dev.txt index c85caacb..88ad60a7 100644 --- a/cdk/apps/ica_credentials/requirements-dev.txt +++ b/cdk/apps/ica_credentials/requirements-dev.txt @@ -1,31 +1,18 @@ -# -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: -# -# pip-compile requirements-dev.in -# -black==21.8b0 - # via -r requirements-dev.in -click==8.0.1 +# This file was autogenerated by uv via the following command: +# uv pip compile requirements-dev.in -o requirements-dev.txt +black==24.3.0 +click==8.1.7 # via black -mypy==0.910 - # via -r requirements-dev.in -mypy-extensions==0.4.3 +mypy==1.9.0 +mypy-extensions==1.0.0 # via # black # mypy -pathspec==0.9.0 +packaging==24.0 # via black -platformdirs==2.3.0 +pathspec==0.12.1 # via black -regex==2021.8.28 +platformdirs==4.2.0 # via black -toml==0.10.2 +typing-extensions==4.11.0 # via mypy -tomli==1.2.1 - # via black -typing-extensions==3.10.0.2 - # via - # -c requirements.txt - # black - # mypy diff --git a/cdk/apps/ica_credentials/requirements.in b/cdk/apps/ica_credentials/requirements.in index dd44e7ee..d97f2491 100644 --- a/cdk/apps/ica_credentials/requirements.in +++ b/cdk/apps/ica_credentials/requirements.in @@ -1,7 +1,12 @@ -aws_cdk.aws_events -aws_cdk.aws_events_targets -aws_cdk.aws_secretsmanager -aws_cdk.aws_lambda -aws_cdk.aws_iam -aws_cdk.core -boto3 +# used for the CDK deployment part of the project +aws-cdk-lib>=2.18.0 +constructs>=10.0.0 + +# used for the lambdas +# we pin these two to match the versions natively in our Python lambda layer +# (you should change these when the Python lambda version changes) +# we do not bundle any requirements for our lambdas which is why it is important +# the version matches what is already in the lambda +boto3==1.34.42 +botocore==1.34.42 +urllib3==1.26.18 diff --git a/cdk/apps/ica_credentials/requirements.txt b/cdk/apps/ica_credentials/requirements.txt index 17675d27..01545e6d 100644 --- a/cdk/apps/ica_credentials/requirements.txt +++ b/cdk/apps/ica_credentials/requirements.txt @@ -1,629 +1,63 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile requirements.in -# -attrs==22.1.0 +# This file was autogenerated by uv via the following command: +# uv pip compile requirements.in -o requirements.txt +attrs==23.2.0 # via # cattrs # jsii -aws-cdk-assets==1.176.0 - # via - # aws-cdk-aws-codebuild - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-s3-assets -aws-cdk-aws-acmpca==1.176.0 - # via aws-cdk-aws-certificatemanager -aws-cdk-aws-apigateway==1.176.0 - # via - # aws-cdk-aws-events-targets - # aws-cdk-aws-route53-targets -aws-cdk-aws-applicationautoscaling==1.176.0 - # via - # aws-cdk-aws-ecs - # aws-cdk-aws-lambda -aws-cdk-aws-autoscaling==1.176.0 - # via - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-ecs - # aws-cdk-aws-events-targets -aws-cdk-aws-autoscaling-common==1.176.0 - # via - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling -aws-cdk-aws-autoscaling-hooktargets==1.176.0 - # via aws-cdk-aws-ecs -aws-cdk-aws-certificatemanager==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-cognito - # aws-cdk-aws-ecs - # aws-cdk-aws-elasticloadbalancingv2 -aws-cdk-aws-cloudformation==1.176.0 - # via aws-cdk-custom-resources -aws-cdk-aws-cloudfront==1.176.0 - # via aws-cdk-aws-route53-targets -aws-cdk-aws-cloudwatch==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-codebuild - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-sns - # aws-cdk-aws-sqs - # aws-cdk-aws-stepfunctions -aws-cdk-aws-codebuild==1.176.0 - # via aws-cdk-aws-events-targets -aws-cdk-aws-codecommit==1.176.0 - # via aws-cdk-aws-codebuild -aws-cdk-aws-codeguruprofiler==1.176.0 - # via aws-cdk-aws-lambda -aws-cdk-aws-codepipeline==1.176.0 - # via aws-cdk-aws-events-targets -aws-cdk-aws-codestarnotifications==1.176.0 - # via - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codepipeline - # aws-cdk-aws-sns -aws-cdk-aws-cognito==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-route53-targets -aws-cdk-aws-ec2==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-autoscaling - # aws-cdk-aws-codebuild - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-custom-resources -aws-cdk-aws-ecr==1.176.0 - # via - # aws-cdk-aws-codebuild - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-lambda -aws-cdk-aws-ecr-assets==1.176.0 - # via - # aws-cdk-aws-codebuild - # aws-cdk-aws-ecs - # aws-cdk-aws-lambda -aws-cdk-aws-ecs==1.176.0 - # via aws-cdk-aws-events-targets -aws-cdk-aws-efs==1.176.0 - # via aws-cdk-aws-lambda -aws-cdk-aws-elasticloadbalancing==1.176.0 - # via - # aws-cdk-aws-autoscaling - # aws-cdk-aws-ecs - # aws-cdk-aws-route53-targets -aws-cdk-aws-elasticloadbalancingv2==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-autoscaling - # aws-cdk-aws-ecs - # aws-cdk-aws-route53-targets - # aws-cdk-aws-servicediscovery -aws-cdk-aws-events==1.176.0 - # via - # -r requirements.in - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codepipeline - # aws-cdk-aws-ecr - # aws-cdk-aws-events-targets - # aws-cdk-aws-lambda - # aws-cdk-aws-s3 - # aws-cdk-aws-sns - # aws-cdk-aws-stepfunctions -aws-cdk-aws-events-targets==1.176.0 - # via -r requirements.in -aws-cdk-aws-globalaccelerator==1.176.0 - # via aws-cdk-aws-route53-targets -aws-cdk-aws-iam==1.176.0 - # via - # -r requirements.in - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-custom-resources -aws-cdk-aws-kinesis==1.176.0 - # via - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesisfirehose -aws-cdk-aws-kinesisfirehose==1.176.0 - # via aws-cdk-aws-events-targets -aws-cdk-aws-kms==1.176.0 - # via - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-codebuild - # aws-cdk-aws-codepipeline - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm -aws-cdk-aws-lambda==1.176.0 - # via - # -r requirements.in - # aws-cdk-aws-apigateway - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cognito - # aws-cdk-aws-ecs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-sns-subscriptions - # aws-cdk-custom-resources -aws-cdk-aws-logs==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-codebuild - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecs - # aws-cdk-aws-events-targets - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-route53 - # aws-cdk-aws-stepfunctions - # aws-cdk-custom-resources -aws-cdk-aws-route53==1.176.0 - # via - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-ecs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-servicediscovery -aws-cdk-aws-route53-targets==1.176.0 - # via aws-cdk-aws-ecs -aws-cdk-aws-s3==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-cloudformation - # aws-cdk-aws-codebuild - # aws-cdk-aws-codepipeline - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3-assets - # aws-cdk-aws-stepfunctions -aws-cdk-aws-s3-assets==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecs - # aws-cdk-aws-lambda - # aws-cdk-aws-logs -aws-cdk-aws-sam==1.176.0 - # via aws-cdk-aws-secretsmanager -aws-cdk-aws-secretsmanager==1.176.0 - # via - # -r requirements.in - # aws-cdk-aws-codebuild - # aws-cdk-aws-ecs -aws-cdk-aws-servicediscovery==1.176.0 - # via aws-cdk-aws-ecs -aws-cdk-aws-signer==1.176.0 - # via aws-cdk-aws-lambda -aws-cdk-aws-sns==1.176.0 - # via - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-cloudformation - # aws-cdk-aws-ecs - # aws-cdk-aws-events-targets - # aws-cdk-aws-lambda - # aws-cdk-aws-sns-subscriptions - # aws-cdk-custom-resources -aws-cdk-aws-sns-subscriptions==1.176.0 - # via - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-events-targets -aws-cdk-aws-sqs==1.176.0 - # via - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-ecs - # aws-cdk-aws-events-targets - # aws-cdk-aws-lambda - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions -aws-cdk-aws-ssm==1.176.0 - # via - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecs -aws-cdk-aws-stepfunctions==1.176.0 - # via - # aws-cdk-aws-apigateway - # aws-cdk-aws-events-targets -aws-cdk-cloud-assembly-schema==1.176.0 - # via - # aws-cdk-aws-ec2 - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-kms - # aws-cdk-aws-route53 - # aws-cdk-aws-ssm - # aws-cdk-core - # aws-cdk-cx-api -aws-cdk-core==1.176.0 - # via - # -r requirements.in - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-codestarnotifications - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-iam - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-sam - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-aws-signer - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-custom-resources -aws-cdk-custom-resources==1.176.0 - # via - # aws-cdk-aws-cognito - # aws-cdk-aws-events-targets - # aws-cdk-aws-route53 -aws-cdk-cx-api==1.176.0 - # via - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-cloudformation - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-iam - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-secretsmanager - # aws-cdk-core -aws-cdk-region-info==1.176.0 - # via - # aws-cdk-aws-codebuild - # aws-cdk-aws-ec2 - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-iam - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-lambda - # aws-cdk-aws-route53-targets - # aws-cdk-core -boto3==1.24.89 - # via -r requirements.in -botocore==1.27.89 +aws-cdk-asset-awscli-v1==2.2.202 + # via aws-cdk-lib +aws-cdk-asset-kubectl-v20==2.1.2 + # via aws-cdk-lib +aws-cdk-asset-node-proxy-agent-v6==2.0.3 + # via aws-cdk-lib +aws-cdk-lib==2.136.0 +boto3==1.34.42 +botocore==1.34.42 # via # boto3 # s3transfer -cattrs==22.1.0 +cattrs==23.2.3 + # via jsii +constructs==10.3.0 + # via aws-cdk-lib +importlib-resources==6.4.0 # via jsii -constructs==3.4.118 - # via - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-codestarnotifications - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-iam - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-sam - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-aws-signer - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-core - # aws-cdk-custom-resources -exceptiongroup==1.0.0rc9 - # via cattrs jmespath==1.0.1 # via # boto3 # botocore -jsii==1.69.0 +jsii==1.96.0 # via - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-codestarnotifications - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-iam - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-sam - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-aws-signer - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-cloud-assembly-schema - # aws-cdk-core - # aws-cdk-custom-resources - # aws-cdk-cx-api - # aws-cdk-region-info + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-kubectl-v20 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-lib # constructs publication==0.0.3 # via - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-codestarnotifications - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-iam - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-sam - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-aws-signer - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-cloud-assembly-schema - # aws-cdk-core - # aws-cdk-custom-resources - # aws-cdk-cx-api - # aws-cdk-region-info + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-kubectl-v20 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-lib # constructs # jsii -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 # via # botocore # jsii -s3transfer==0.6.0 +s3transfer==0.10.1 # via boto3 six==1.16.0 # via python-dateutil typeguard==2.13.3 # via - # aws-cdk-assets - # aws-cdk-aws-apigateway - # aws-cdk-aws-applicationautoscaling - # aws-cdk-aws-autoscaling - # aws-cdk-aws-autoscaling-common - # aws-cdk-aws-autoscaling-hooktargets - # aws-cdk-aws-certificatemanager - # aws-cdk-aws-cloudformation - # aws-cdk-aws-cloudwatch - # aws-cdk-aws-codebuild - # aws-cdk-aws-codecommit - # aws-cdk-aws-codeguruprofiler - # aws-cdk-aws-codepipeline - # aws-cdk-aws-codestarnotifications - # aws-cdk-aws-cognito - # aws-cdk-aws-ec2 - # aws-cdk-aws-ecr - # aws-cdk-aws-ecr-assets - # aws-cdk-aws-ecs - # aws-cdk-aws-efs - # aws-cdk-aws-elasticloadbalancing - # aws-cdk-aws-elasticloadbalancingv2 - # aws-cdk-aws-events - # aws-cdk-aws-events-targets - # aws-cdk-aws-iam - # aws-cdk-aws-kinesis - # aws-cdk-aws-kinesisfirehose - # aws-cdk-aws-kms - # aws-cdk-aws-lambda - # aws-cdk-aws-logs - # aws-cdk-aws-route53 - # aws-cdk-aws-route53-targets - # aws-cdk-aws-s3 - # aws-cdk-aws-s3-assets - # aws-cdk-aws-sam - # aws-cdk-aws-secretsmanager - # aws-cdk-aws-servicediscovery - # aws-cdk-aws-signer - # aws-cdk-aws-sns - # aws-cdk-aws-sns-subscriptions - # aws-cdk-aws-sqs - # aws-cdk-aws-ssm - # aws-cdk-aws-stepfunctions - # aws-cdk-cloud-assembly-schema - # aws-cdk-core - # aws-cdk-custom-resources - # aws-cdk-cx-api - # aws-cdk-region-info + # aws-cdk-asset-awscli-v1 + # aws-cdk-asset-kubectl-v20 + # aws-cdk-asset-node-proxy-agent-v6 + # aws-cdk-lib # constructs # jsii -typing-extensions==4.4.0 +typing-extensions==4.11.0 # via jsii -urllib3==1.26.12 +urllib3==1.26.18 # via botocore diff --git a/cdk/apps/ica_credentials/scripts/install-deps.sh b/cdk/apps/ica_credentials/scripts/install-deps.sh deleted file mode 100755 index 8f8a6bcb..00000000 --- a/cdk/apps/ica_credentials/scripts/install-deps.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -set -o errexit -set -o verbose - -# Install project dependencies -pip install -r requirements.txt -r requirements-dev.txt diff --git a/cdk/apps/ica_credentials/secrets/runtime/notify_slack/lambda_entrypoint.py b/cdk/apps/ica_credentials/secrets/runtime/notify_slack/lambda_entrypoint.py deleted file mode 100644 index f9f91549..00000000 --- a/cdk/apps/ica_credentials/secrets/runtime/notify_slack/lambda_entrypoint.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -from typing import Any - -from notify_slack import send_secrets_event_to_slack - - -def main(ev: Any, _: Any) -> Any: - """ - Send a notification to Slack for an event that occurred in Secrets Manager - """ - print(f"Starting slack notifier for Secrets Manager") - - slack_host_ssm_name = os.environ["SLACK_HOST_SSM_NAME"] - - if not slack_host_ssm_name: - raise Exception("SLACK_HOST_SSM_NAME must be specified in the Notify Slack lambda environment") - - slack_webhook_ssm_name = os.environ["SLACK_WEBHOOK_SSM_NAME"] - - if not slack_webhook_ssm_name: - raise Exception("SLACK_WEBHOOK_SSM_NAME must be specified in the Notify Slack lambda environment") - - send_secrets_event_to_slack(ev, slack_host_ssm_name, slack_webhook_ssm_name)