Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Cdkts/pieriandx password stack #409

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions cdk/apps/pieriandx_credentials/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions cdk/apps/pieriandx_credentials/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
14 changes: 14 additions & 0 deletions cdk/apps/pieriandx_credentials/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
60 changes: 60 additions & 0 deletions cdk/apps/pieriandx_credentials/bin/pieriandx_credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { PieriandxCredentialsStack } from '../lib/pieriandx_credentials-stack';
import {
PIERIANDX_BASE_URL_DEV,
PIERIANDX_INSTITUTION_DEV,
PIERIANDX_API_KEYNAME,
SECRETS_SSM_ROOT,
SLACK_HOST_SSM_NAME,
SLACK_WEBHOOK_SSM_NAME,
PIERIANDX_JWT_KEYNAME,
PIERIANDX_JWT_COLLECTION_FUNCTION_NAME,
PIERIANDX_S3_KEYNAME,
PIERIANDX_BASE_URL_PROD,
PIERIANDX_INSTITUTION_PROD
} from "../constants";

const app = new cdk.App();


const DEV_ACCOUNT_ID = "843407916570"
const DEV_ACCOUNT_REGION = "ap-southeast-2"
const PROD_ACCOUNT_ID = "472057503814"
const PROD_ACCOUNT_REGION = "ap-southeast-2"

/* Development Stack */
new PieriandxCredentialsStack(app, 'PieriandxCredentialsStackDev', {
pieriandx_base_url: PIERIANDX_BASE_URL_DEV,
pieriandx_institution: PIERIANDX_INSTITUTION_DEV,
api_key_name: PIERIANDX_API_KEYNAME,
jwt_key_name: PIERIANDX_JWT_KEYNAME,
s3_key_name: PIERIANDX_S3_KEYNAME,
key_ssm_root: SECRETS_SSM_ROOT,
collect_function_name: PIERIANDX_JWT_COLLECTION_FUNCTION_NAME,
slack_host_ssm_name: SLACK_HOST_SSM_NAME,
slack_webhook_ssm_name: SLACK_WEBHOOK_SSM_NAME,
env: {
account: DEV_ACCOUNT_ID,
region: DEV_ACCOUNT_REGION
}
});

/* Production Stack */
new PieriandxCredentialsStack(app, 'PieriandxCredentialsStackProd', {
pieriandx_base_url: PIERIANDX_BASE_URL_PROD,
pieriandx_institution: PIERIANDX_INSTITUTION_PROD,
api_key_name: PIERIANDX_API_KEYNAME,
jwt_key_name: PIERIANDX_JWT_KEYNAME,
s3_key_name: PIERIANDX_S3_KEYNAME,
key_ssm_root: SECRETS_SSM_ROOT,
collect_function_name: PIERIANDX_JWT_COLLECTION_FUNCTION_NAME,
slack_host_ssm_name: SLACK_HOST_SSM_NAME,
slack_webhook_ssm_name: SLACK_WEBHOOK_SSM_NAME,
env: {
account: PROD_ACCOUNT_ID,
region: PROD_ACCOUNT_REGION
}
});

68 changes: 68 additions & 0 deletions cdk/apps/pieriandx_credentials/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"app": "npx ts-node --prefer-ts-exts bin/pieriandx_credentials.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true
}
}
20 changes: 20 additions & 0 deletions cdk/apps/pieriandx_credentials/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/* Prod Constants */
export const PIERIANDX_BASE_URL_PROD = "https://app.pieriandx.com/cgw-api";
export const PIERIANDX_INSTITUTION_PROD = "melbourne"

/* Dev Constants */
export const PIERIANDX_BASE_URL_DEV = "https://app.uat.pieriandx.com/cgw-api";
export const PIERIANDX_INSTITUTION_DEV = "melbournetest"

/* Communal Constants */
export const PIERIANDX_API_KEYNAME = "PierianDx/ApiKey"
export const PIERIANDX_JWT_KEYNAME = "PierianDx/JwtKey"
export const PIERIANDX_S3_KEYNAME= "PierianDx/S3Credentials"

export const PIERIANDX_JWT_COLLECTION_FUNCTION_NAME = "collectPierianDxAccessToken"

export const SLACK_HOST_SSM_NAME = "/slack/webhook/host"
export const SLACK_WEBHOOK_SSM_NAME = "/slack/webhook/id"

export const SECRETS_SSM_ROOT = "/pieriandx/secrets"
8 changes: 8 additions & 0 deletions cdk/apps/pieriandx_credentials/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3

from typing import Any, Optional, Dict
from pieriandx_secrets_tools.secret_manager_common import (
get_current_jwt_token, initiate_jwt_secret_rotation, get_jwt_token_status
)

from pieriandx_secrets_tools.pieriandx_common import jwt_is_valid
import time


def main(ev: Any, _: Any) -> Optional[Dict]:
"""
Uses a master API key secret to generate new JWTs. In order that we can use a single
lambda, there are various environment variables that parametrise how the lambda works.

Required envs
PIERIANDX_JWT_KEYNAME the JWT Key name of a secret from which we can get an PIERIANDX JWT Token
"""

# Get the secret status (check if 'AWS_PENDING' or 'AWS_CURRENT')
if not get_jwt_token_status():
# In rotating state
return None

# Get the secret value
access_token = get_current_jwt_token()

if not jwt_is_valid(access_token):
# Rotate the secret
initiate_jwt_secret_rotation()
return None

return {"auth_token": access_token}


if __name__ == "__main__":
main({}, None)
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3

import os
from typing import Any

import boto3

from pieriandx_secrets_tools.secret_manager_common import (
do_finish_secret,
do_create_secret,
check_rotation_state,
get_email_password_institution
)

from pieriandx_secrets_tools.pieriandx_common import credentials_to_jwt


def main(ev: Any, _: Any) -> Any:
"""
Uses a master API key secret to generate new JWTs. In order that we can use a single
lambda, there are various environment variables that parametrise how the lambda works.

Required
API_KEY_AWS_SECRETS_MANAGER_ARN the arn of a secret from which we can get an ICA API key
ICA_BASE_URL the url of the ICA endpoint

Params
PROJECT_ID or PROJECT_IDS the projects to ask for in the JWT - this alters the format of the
resulting Secret (either straight JWT *or* dictionary)
"""
arn = ev["SecretId"]
tok = ev["ClientRequestToken"]
step = ev["Step"]

print(f"Starting step '{step}' of secret '{arn}' and version '{tok}'")

master_secret_name = os.environ["PIERIANDX_API_KEYNAME"]

if not master_secret_name:
raise Exception("API_KEY_AWS_SECRETS_MANAGER_ARN must be specified in the JWT producer")

# Add PierianDx base url
pieriandx_base_url = os.environ["PIERIANDX_BASE_URL"]

if not pieriandx_base_url:
raise Exception("PIERIANDX_BASE_URL must be specified in the JWT producer")

# Get secrets manager client
sm_client = boto3.client("secretsmanager")

# Get master api key
master_email, master_password, institution = get_email_password_institution()

access_token = credentials_to_jwt(
pieriandx_base_url=pieriandx_base_url,
email=master_email,
password=master_password,
institution=institution
)

# the rotation state checker will also let us know if we should skip processing entirely
if check_rotation_state(arn, tok):
print(
f"Successfully skipped step '{step}' of secret '{arn}' and version '{tok}' because of state information"
)
return

if step == "createSecret":
do_create_secret(
arn=arn,
tok=tok,
access_token=access_token
)

elif step == "setSecret":
pass
elif step == "testSecret":
pass
elif step == "finishSecret":
do_finish_secret(arn, tok)
else:
raise ValueError("Invalid step parameter")

print(f"Successfully finished step '{step}' of secret '{arn}' and version '{tok}'")
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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)