{"id":45914798,"url":"https://github.com/time-loop/cdk-aurora","last_synced_at":"2026-02-28T07:33:45.195Z","repository":{"id":37057604,"uuid":"479560309","full_name":"time-loop/cdk-aurora","owner":"time-loop","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-23T01:20:54.000Z","size":1221,"stargazers_count":4,"open_issues_count":11,"forks_count":0,"subscribers_count":30,"default_branch":"main","last_synced_at":"2026-02-23T07:35:37.889Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/time-loop.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-04-08T22:55:47.000Z","updated_at":"2025-12-18T03:49:13.000Z","dependencies_parsed_at":"2024-02-16T01:29:05.147Z","dependency_job_id":"21913173-6767-43d7-af4e-a342c6ec5778","html_url":"https://github.com/time-loop/cdk-aurora","commit_stats":null,"previous_names":[],"tags_count":112,"template":false,"template_full_name":null,"purl":"pkg:github/time-loop/cdk-aurora","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/time-loop%2Fcdk-aurora","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/time-loop%2Fcdk-aurora/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/time-loop%2Fcdk-aurora/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/time-loop%2Fcdk-aurora/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/time-loop","download_url":"https://codeload.github.com/time-loop/cdk-aurora/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/time-loop%2Fcdk-aurora/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29927583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T19:37:42.220Z","status":"online","status_checked_at":"2026-02-28T02:00:07.010Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-28T07:33:44.004Z","updated_at":"2026-02-28T07:33:45.188Z","avatar_url":"https://github.com/time-loop.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![codecov](https://codecov.io/gh/time-loop/cdk-aurora/branch/main/graph/badge.svg?token=gj4E1luEdC)](https://codecov.io/gh/time-loop/cdk-aurora)\n\n# cdk-aurora\n\n## WARNINGS\n\n### Proxy / Multiuser rotation incompatibility\n\nThe multiuser rotation scheme works by having two actual user roles for each \"user\".\nThe second user role is just the original with `_clone` appended to the name.\nFor this discussion we'll call them `user` and `user_clone`.\n\nAt any given time, the SecretsManager secret has the name and password for one of these two users.\nWhen the rotation lambda is triggered, it pulls the secret to find out which user is currently active.\nNext it generates a new password for the inactive user and updates it in the database.\nFinally, it updates the secret to swap users and provides the newly activated user's password.\nSee https://aws.amazon.com/blogs/database/multi-user-secrets-rotation-for-amazon-rds/ for details.\n\nHere's an example of a rotation:\nLet's assume that `user_clone` is currently active.\n- generate a new password and assign it to `user` in the database.\n- update Secret to have `user` as the active user and provide the new password.\n\nThe application always, even if it fetched the secret several days ago, can connect to the database.\nThe whole point of this approach is to provide a window of opportunity for the application to update it's connection string.\n\nMEANWHILE\n\nRDS Proxy receives a list of SecretsManager secrets for access management:\nhttps://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_rds.DatabaseProxy.html#secrets\n\nWhen the rotation happens, the secret is updated.\nRDS Proxy says \"ok, so, `user_clone` is no longer allowed to connect. Instead `user` is allowed to connect.\"\nWhich makes perfect sense, except... the application still wants to connect as `user_clone`.\nThe \"window of opportunity\" has been slammed closed and we have effectively the \"single user rotation\" pattern.\n\n## Example deploy with connection via JumpHost\n\n```ts\nimport { Aurora } from '@time-loop/cdk-aurora';\nimport { JumpBox } from '@time-loop/cdk-jump-box';\nimport { App, aws_ec2, aws_kms, Stack, StackProps } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\nimport { Namer } from 'multi-convention-namer';\n\nexport class AuroraDemoStack extends Stack {\n  constructor(scope: Construct, props: StackProps) {\n    const id = new Namer(['aurora', 'demo']);\n    super(scope, id.pascal, props);\n\n    const vpc = aws_ec2.Vpc.fromLookup(this, 'Vpc', {\n      isDefault: true,\n    });\n\n    const kmsKey = new aws_kms.Key(this, 'Key', {\n      description: `${id.pascal} encryption key`,\n    });\n\n    const a = new Aurora(this, id, {\n      defaultDatabaseName: 'demo',\n      instances: 1, // It's just a demo\n      kmsKey,\n      vpc,\n    });\n    const j = new JumpBox(this, id.addSuffix(['jump']), { kmsKey, sshAccess: true, vpc: this.vpc });\n    a.cluster.connections.allowDefaultPortFrom(j.securityGroup);\n    a.proxy!.connections.allowFrom(j.securityGroup, aws_ec2.Port.tcp(5432));\n  }\n}\n\n// for development, use account/region from cdk cli\nconst devEnv = {\n  account: process.env.CDK_DEFAULT_ACCOUNT,\n  region: process.env.CDK_DEFAULT_REGION,\n};\n\nconst app = new App();\n\nnew AuroraDemoStack(app, { env: devEnv });\n\napp.synth();\n```\n\n\n### Bootstrapping\n1. [Install](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) the session manager plugin:\n\n```bash\ncurl \"https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip\" -o \"sessionmanager-bundle.zip\"\nunzip sessionmanager-bundle.zip\n\n# use python3 instead of python2 on your mac\npython3 sessionmanager-bundle/install\n```\n\n2. Make sure the following is in your `~/.ssh/config`:\n\n```\n# SSH over Session Manager\nHost i-* mi-*\n  ProxyCommand sh -c \"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'\"\n```\n\n3. Get the SSH key such as `~/.ssh/myAwsProfile-AuroraDemoJump.pem`:\n\n```bash\nexport AWS_PROFILE=myAwsProfile\nexport AWS_REGION=us-west-2\n\nSTACK_NAME=\"AuroraDemo\"\nASG_NAME=\"${STACK_NAME}Jump\"\n\n# Fetch the SSH key from SecretsManager\nSSH_KEY_NAME=\"$HOME/.ssh/${AWS_PROFILE}-${ASG_NAME}.pem\"\naws secretsmanager get-secret-value --region=us-west-2 --output=text --query SecretString --secret-id \"ec2-ssh-key/${ASG_NAME}/private\" \u003e \"$SSH_KEY_NAME\"\nchmod 400 \"$SSH_KEY_NAME\"\n```\n\n### To Connect\n\n```bash\nexport AWS_PROFILE=myAwsProfile\nexport AWS_REGION=us-west-2\n\nSTACK_NAME=\"AuroraDemo\"\nASG_NAME=\"${STACK_NAME}Jump\"\n\nSSH_KEY_NAME=\"$HOME/.ssh/${AWS_PROFILE}-${ASG_NAME}.pem\"\n\n# Set us up the Jumpbox\naws autoscaling set-desired-capacity --auto-scaling-group-name \"$ASG_NAME\" --desired-capacity 1\n\n# Find the jump box instance\nwhile\n  JUMP_INSTANCE_ID=$(aws autoscaling describe-auto-scaling-groups --query \"AutoScalingGroups[?AutoScalingGroupName=='$ASG_NAME'].Instances[].InstanceId\" --output=text)\n  [[ -z \"$JUMP_INSTANCE_ID\" ]];\ndo\n  sleep 10\ndone\n\n# Pull the connection information secret as a JSON blob\nDBROLE=\"Reader\" # Also supports Writer and Manager (for DDL)\nSECRET=$(aws secretsmanager get-secret-value --region=us-west-2 --output=text --query SecretString --secret-id \"${STACK_NAME}${DBROLE}\")\n\n# Parse the JSON blob (jq is cleaner, but everyone already has node)\nDBUSER=$(node -pe 'JSON.parse(require(\"fs\").readFileSync(\"/dev/stdin\").toString()).username' \u003c\u003c\u003c \"$SECRET\")\nPASSWORD=$(node -pe 'JSON.parse(require(\"fs\").readFileSync(\"/dev/stdin\").toString()).password' \u003c\u003c\u003c \"$SECRET\")\n\n# The manager user connects directly to the aurora cluster\nif [[ -z \"${DBUSER##*_manager}\" ]]; then\n  HOST=$(node -pe 'JSON.parse(require(\"fs\").readFileSync(\"/dev/stdin\").toString()).host' \u003c\u003c\u003c \"$SECRET\")\n  PORT=$(node -pe 'JSON.parse(require(\"fs\").readFileSync(\"/dev/stdin\").toString()).port' \u003c\u003c\u003c \"$SECRET\")\nelse\n  HOST=$(aws cloudformation describe-stacks --stack-name \"$STACK_NAME\" --query \"Stacks[0].Outputs[?ExportName=='${STACK_NAME}ProxyEndpoint'].OutputValue\" --output=text)\n  PORT=5432\nfi\n\nif grep -F \"$DBUSER\" ~/.pgpass \u003e /dev/null; then\n  echo \"Found $DBUSER in ~/.pgpass\"\nelse\n  echo \"Adding $DBUSER to ~/.pgpass\"\n  echo \"*:*:*:$DBUSER:$PASSWORD\" \u003e\u003e ~/.pgpass\n  chmod 0600 ~/.pgpass\nfi\n\n# Find an unused local port\n# https://unix.stackexchange.com/a/132524/119704 (see first comment)\nLOCAL_PORT=$(python -c 'import socket; s=socket.socket(); s.bind((\"\", 0)); print(s.getsockname()[1]); s.close()')\n\n# Create an auto-closing tunnel, then connect using psql\n# See http://www.g-loaded.eu/2006/11/24/auto-closing-ssh-tunnels/ for details\nssh -f -i \"$SSH_KEY_NAME\" -L \"$LOCAL_PORT:$HOST:$PORT\" \"ec2-user@$JUMP_INSTANCE_ID\"  sleep 10 \u0026\u0026 \\\npsql --host localhost --port \"$LOCAL_PORT\" --username \"$DBUSER\" clickup\n\n#########################################################\n# Alternatively, you can run the tunnel command by it'self as follows\n# if you wanted to run your code locally and have it connect\n# To do that,\n# - remove the `sleep 10` command\n# - add the -N parameter to the command\n# This creates a tunnel that is not ephemeral.\n# You will have to manage the tunnel yourself,\n# specifically kill the SSH session when you're done using the tunnel.\n# You can put LOCAL_PORT=5432 if don't already have postgres running locally.\n\nssh -N -f -i \"$SSH_KEY\" -L \"$LOCAL_PORT:$HOST:$PORT\" \"ec2-user@$JUMP_INSTANCE_ID\"\n```\n\n## Troubleshooting\n\n### DB Provisioner\n\nDid your DB provisioner not run?\nCheck the Lambda's logfiles, you should see the grants it ran.\nTo manually fix issues, you can run the following commands:\n\n```\n# Show database connect privs\n\\l\n\n# grant db connect privs\nGRANT CONNECT ON DATABASE foo TO r_reader\nGRANT CONNECT ON DATABASE foo TO r_writer\n\n# Show schema privs\n\\dn+\n\n# Grant access to schemas\nGRANT USAGE ON SCHEMA task_mgmt TO r_reader;\nGRANT USAGE ON SCHEMA task_mgmt TO r_writer;\n\n# Show default privs\n\\ddp\n\n# Add missing defaults\nALTER DEFAULT PRIVILEGES GRANT USAGE ON SEQUENCES TO r_reader;\nALTER DEFAULT PRIVILEGES GRANT USAGE ON SEQUENCES TO r_writer;\n\nALTER DEFAULT PRIVILEGES GRANT SELECT ON TABLES TO r_reader;\nALTER DEFAULT PRIVILEGES GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO r_writer;\n\n# Show table privs\nSET search_path TO task_mgmt;\n\\dp\n\n# Fix perms for tables which should have been defaulted:\ngrant select on all tables in schema task_mgmt to r_reader;\ngrant select, insert, update, delete on all tables in schema task_mgmt to r_writer;\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftime-loop%2Fcdk-aurora","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftime-loop%2Fcdk-aurora","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftime-loop%2Fcdk-aurora/lists"}