diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5df0e159..4946aa41 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,8 +7,8 @@ on: - develop jobs: - deploy: - runs-on: ubuntu-20.04 + build-push: + runs-on: ubuntu-22.04 # standard (not self-hosted) runner env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -21,24 +21,82 @@ jobs: with: python-version: '3.10' cache: 'pip' - cache-dependency-path: 'requirements/*/**.txt' + cache-dependency-path: 'requirements/*/dev.txt' - name: Install dependencies id: pip-install run: | python -m pip install --upgrade pip wheel pip-tools - pip-sync requirements/base/base.txt requirements/dev/dev.txt + pip-sync requirements/dev/dev.txt - name: Login to Docker id: docker-login run: | inv aws.docker-login - - name: Build, tag, push, and deploy image - id: build-tag-push-deploy + - name: Set DOCKER_TAG and save to file for artifact upload + run: | + DOCKER_TAG=$(inv image.tag | grep 'Set config.tag to' | cut -d' ' -f4) + echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV + echo "$DOCKER_TAG" > docker-tag.txt + - name: Build, tag, and push image + id: build-tag-push run: | [ "$GITHUB_REF" = refs/heads/main ] && ENV="production" || ENV="staging" echo "env is $ENV" - inv $ENV image deploy --verbosity=0 + export BUILDKIT_PROGRESS=plain + inv $ENV image.build --tag=${{ env.DOCKER_TAG }} image.push --tag=${{ env.DOCKER_TAG }} + - name: Upload docker tag from build-push job + uses: actions/upload-artifact@v4 + with: + name: docker_tag + path: docker-tag.txt + + # The deploy needs to run from within the cluster, since the cluster + # is not exposed to the public internet. This step is split out into + # its own job to reduce the amount of work done on the self-hosted runner + # and avoid the need to run a privileged docker container (with the + # capability of building a docker container itself). + deploy: + runs-on: arc-runner-set # K8s self-hosted runner + needs: [build-push] + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + # https://github.com/marketplace/actions/slack-github-actions-slack-integration + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + steps: + - uses: actions/checkout@v3 + - name: Download docker tag from build-push job + uses: actions/download-artifact@v4 + with: + name: docker_tag + - name: Set variables + run: | + DOCKER_TAG=$(cat docker-tag.txt) + echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV + - name: Install apt packages + run: | + sudo apt-get update + sudo apt-get install -y git + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: 'requirements/*/dev.txt' + - name: Install dependencies + id: pip-install + run: | + python -m pip install --upgrade pip wheel pip-tools + pip-sync requirements/dev/dev.txt + - name: Deploy the image + id: deploy + run: | + [ "$GITHUB_REF" = refs/heads/main ] && + ENV="production" || + ENV="staging" + echo "env is $ENV" --verbosity=0 + inv $ENV deploy --tag=${{ env.DOCKER_TAG }} --verbosity=0 - uses: act10ns/slack@v1 with: status: ${{ job.status }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dafab2e0..7307f365 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,13 +2,12 @@ name: Lint and Test code on: pull_request: - schedule: - # run once a week on early monday mornings - - cron: '22 2 * * 1' + push: + branches: [main, develop] jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 # standard (not self-hosted) runner env: DJANGO_SETTINGS_MODULE: hip.settings.dev services: diff --git a/Dockerfile b/Dockerfile index 286cd651..5f56ebd7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -121,8 +121,8 @@ RUN groupadd --gid $USER_GID $USERNAME \ # openssh-client -- for git over SSH # sudo -- to run commands as superuser # vim -- enhanced vi editor for commits -ENV KUBE_CLIENT_VERSION="v1.25.10" -ENV HELM_VERSION="3.12.0" +ENV KUBE_CLIENT_VERSION="v1.29.3" +ENV HELM_VERSION="3.14.4" RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ --mount=type=cache,mode=0755,target=/root/.cache/pip \ set -ex \ diff --git a/README.md b/README.md index 1327fe3f..0242c846 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ These instructions assume that you will be working with dockerized services. First add the following line to your `.env` file: ```sh -(hip)$ echo "DATABASE_URL=postgres://postgres@127.0.0.1:5433/hip" >> .env +(hip)$ echo "DATABASE_URL=postgres://postgres@127.0.0.1:5432/hip" >> .env ``` The `docker-compose.yml` sets up environment variables in a file, ``.postgres``. @@ -294,3 +294,22 @@ To reset your local database from a deployed environment: As mentioned in the Database setup instructions, you may need to visit [/cms/sites](http://localhost:8000/cms/sites/) and change the first entry's `Hostname` field to `localhost` to enable page previews in the Wagtail admin. + +### GitHub Actions Runner + +There are [GitHub Actions self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) deployed in the Kubernetes cluster along side the application. + +Setup instructions: + +* Obtain a [GitHub PAT](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with the `repo` scope that's valid for one week (it needs to be active only for the initial deployment). Add this to a local environment variable `RUNNER_CFG_PAT`: + +```sh +export RUNNER_CFG_PAT="gh......" +``` + +* Run the playbook to deploy the runner: + +```sh +cd deploy/ +ansible-playbook deploy-runner.yml +``` diff --git a/apps/reports/tests/test_models.py b/apps/reports/tests/test_models.py index 061b7a17..ca69194a 100644 --- a/apps/reports/tests/test_models.py +++ b/apps/reports/tests/test_models.py @@ -84,23 +84,28 @@ def test_datareportslistpage_context_only_external_reports(db, rf): "last_updated": date(year=2021, month=1, day=1), "external": True, } + external_reports_data = [ + { + "type": "external_reports", # Block type + "value": { + "title": external_report_hiv["title"], + "url": external_report_hiv["url"], + "update_frequency": external_report_hiv["update_frequency"], + "last_updated": external_report_hiv["last_updated"], + }, + }, + { + "type": "external_reports", + "value": { + "title": external_report_hep_a["title"], + "url": external_report_hep_a["url"], + "update_frequency": external_report_hep_a["update_frequency"], + "last_updated": external_report_hep_a["last_updated"], + }, + }, + ] reports_list_page = DataReportListPageFactory( - external_reports__0__external_reports__title=external_report_hiv["title"], - external_reports__0__external_reports__url=external_report_hiv["url"], - external_reports__0__external_reports__update_frequency=external_report_hiv[ - "update_frequency" - ], - external_reports__0__external_reports__last_updated=external_report_hiv[ - "last_updated" - ], - external_reports__1__external_reports__title=external_report_hep_a["title"], - external_reports__1__external_reports__url=external_report_hep_a["url"], - external_reports__1__external_reports__update_frequency=external_report_hep_a[ - "update_frequency" - ], - external_reports__1__external_reports__last_updated=external_report_hep_a[ - "last_updated" - ], + external_reports=external_reports_data ) context = reports_list_page.get_context(rf.get("/someurl/")) @@ -127,15 +132,19 @@ def test_datareportslistpage_context_internal_and_external_reports(db, rf): "last_updated": "2021-01-01", "external": True, } + external_reports_data = [ + { + "type": "external_reports", + "value": { + "title": external_report_hiv["title"], + "url": external_report_hiv["url"], + "update_frequency": external_report_hiv["update_frequency"], + "last_updated": external_report_hiv["last_updated"], + }, + }, + ] reports_list_page = DataReportListPageFactory( - external_reports__0__external_reports__title=external_report_hiv["title"], - external_reports__0__external_reports__url=external_report_hiv["url"], - external_reports__0__external_reports__update_frequency=external_report_hiv[ - "update_frequency" - ], - external_reports__0__external_reports__last_updated=external_report_hiv[ - "last_updated" - ], + external_reports=external_reports_data ) # Create some internal reports (DataReportDetailPages) for the DataReportListPage. tuberculosis = DiseaseAndConditionDetailPageFactory(title="Tuberculosis") diff --git a/deploy/deploy-cluster.yml b/deploy/deploy-cluster.yml index 89bc100d..b0db5604 100644 --- a/deploy/deploy-cluster.yml +++ b/deploy/deploy-cluster.yml @@ -10,7 +10,7 @@ tasks: - name: Add AWS for fluent bit helm chart (centralized logging) tags: fluentbit - community.kubernetes.helm: + kubernetes.core.helm: context: "{{ k8s_context|mandatory }}" kubeconfig: "{{ k8s_kubeconfig }}" chart_repo_url: "https://aws.github.io/eks-charts" @@ -29,7 +29,7 @@ wait: yes - name: Create Amazon CloudWatch Metrics namespace tags: cloudwatch - community.kubernetes.k8s: + kubernetes.core.k8s: context: "{{ k8s_context|mandatory }}" kubeconfig: "{{ k8s_kubeconfig }}" name: "{{ k8s_aws_cloudwatch_metrics_namespace }}" @@ -38,7 +38,7 @@ state: present - name: Add AWS CloudWatch Metrics helm chart (monitoring) tags: cloudwatch - community.kubernetes.helm: + kubernetes.core.helm: context: "{{ k8s_context|mandatory }}" kubeconfig: "{{ k8s_kubeconfig }}" chart_repo_url: "https://aws.github.io/eks-charts" @@ -54,6 +54,7 @@ tags: cloudwatch amazon.aws.cloudwatch_metric_alarm: state: present + aws_profile: "{{ aws_profile }}" region: us-east-1 name: "{{ item.name }}" description: "{{ item.description }}" diff --git a/deploy/deploy-runner.yml b/deploy/deploy-runner.yml new file mode 100644 index 00000000..924a1ad5 --- /dev/null +++ b/deploy/deploy-runner.yml @@ -0,0 +1,47 @@ +--- +- name: Install Actions Runner Controller and configure runner scale set + hosts: cluster + vars: + ansible_connection: local + ansible_python_interpreter: "{{ ansible_playbook_python }}" + runner_namespace: github-runner + chart_version: "0.9.3" + gather_facts: false + tasks: + # https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners-with-actions-runner-controller/quickstart-for-actions-runner-controller + # + # Ansible task to automate: + # helm install arc \ + # --namespace "${NAMESPACE}" \ + # --create-namespace \ + # oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller + - name: Installing Actions Runner Controller + kubernetes.core.helm: + context: "{{ k8s_context|mandatory }}" + chart_ref: oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller + chart_version: "{{ chart_version }}" + release_name: arc + release_namespace: "{{ runner_namespace }}" + create_namespace: true + wait: yes + + # Ansible task to automate: + # helm install "${INSTALLATION_NAME}" \ + # --namespace "${NAMESPACE}" \ + # --create-namespace \ + # --set githubConfigUrl="https://github.com/caktus/philly-hip" \ + # --set githubConfigSecret.github_token="${RUNNER_CFG_PAT}" \ + # oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set + - name: Configuring a runner scale set + kubernetes.core.helm: + context: "{{ k8s_context|mandatory }}" + chart_ref: oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set + chart_version: "{{ chart_version }}" + release_name: arc-runner-set + release_namespace: "{{ runner_namespace }}" + create_namespace: true + release_values: + githubConfigUrl: "https://github.com/caktus/philly-hip" + githubConfigSecret: + github_token: "{{ lookup('env', 'RUNNER_CFG_PAT') }}" + wait: yes diff --git a/deploy/group_vars/all.yml b/deploy/group_vars/all.yml index d072bd67..e94c8890 100644 --- a/deploy/group_vars/all.yml +++ b/deploy/group_vars/all.yml @@ -11,12 +11,19 @@ stack_name: "{{ long_app_name }}-stack" aws_profile: "{{ long_app_name }}" cluster_name: "{{ stack_name }}-cluster" -ansible_connection: local -ansible_python_interpreter: "{{ ansible_playbook_python }}" +# ansible_connection: local +# ansible_python_interpreter: "{{ ansible_playbook_python }}" k8s_cluster_name: "{{ cluster_name }}" k8s_namespace: "{{ app_name }}-{{ env_name }}" +# IP addresses +administrator_ip_cidrs: + - 70.62.97.170/32 # Caktus office + - 107.15.45.253/32 # Tobias + - 47.230.11.28/32 # Ronard + - 107.223.185.195/32 # Colin + # CloudFormation Outputs # These values are taken from the CF 'Output' tab # aws eks describe-cluster --name=philly-hip-stack-cluster | grep endpoint @@ -45,7 +52,7 @@ cloudformation_stack: region: "{{ aws_region }}" stack_name: "{{ stack_name }}" template_bucket: "aws-web-stacks-{{ app_name }}" - template_local_path: '{{ playbook_dir + "/stack/eks-no-nat.yml" }}' + template_local_path: '{{ playbook_dir + "/stack/eks-nat.yml" }}' create_changeset: true termination_protection: true @@ -68,6 +75,8 @@ cloudformation_stack: DatabaseUser: "{{ app_name }}_admin" DatabasePassword: "{{ admin_database_password }}" DatabaseMultiAZ: "true" + EksClusterName: "philly-hip-stack-cluster" + EksPublicAccessCidrs: " {{ administrator_ip_cidrs | join(',') }}" tags: Environment: "{{ app_name }}" @@ -78,18 +87,18 @@ cloudformation_stack: k8s_cluster_type: aws # aws eks describe-cluster --name=philly-hip-stack-cluster --query 'cluster.arn' k8s_context: "arn:aws:eks:us-east-1:061553509755:cluster/{{ cluster_name }}" -k8s_ingress_nginx_chart_version: "4.6.0" -k8s_cert_manager_chart_version: "v1.11.1" +k8s_ingress_nginx_chart_version: "4.9.1" +k8s_cert_manager_chart_version: "v1.14.3" k8s_letsencrypt_email: admin@caktusgroup.com k8s_iam_users: [noop] # https://github.com/caktus/ansible-role-k8s-web-cluster/issues/17 # aws-for-fluent-bit # - https://github.com/aws/eks-charts/tree/master/stable/aws-for-fluent-bit # - https://artifacthub.io/packages/helm/aws/aws-for-fluent-bit -k8s_aws_fluent_bit_chart_version: "0.1.18" +k8s_aws_fluent_bit_chart_version: "0.1.32" # aws-cloudwatch-metrics: # - https://github.com/aws/eks-charts/tree/master/stable/aws-cloudwatch-metrics # - https://artifacthub.io/packages/helm/aws/aws-cloudwatch-metrics -k8s_aws_cloudwatch_metrics_chart_version: "0.0.9" +k8s_aws_cloudwatch_metrics_chart_version: "0.0.11" k8s_aws_cloudwatch_metrics_namespace: amazon-cloudwatch # ---------------------------------------------------------------------------- @@ -168,16 +177,16 @@ env_email_host_pass: !vault | 3561616461636134373033316665613035303736646133613630 # Azure SSO settings -azure_client_id: "f0629cf8-f6f4-4142-94c3-11b8beaaa510" +azure_client_id: "39459c9f-77b3-4fae-942c-7ef9fbf1332c" azure_tenant_id: "2046864f-68ea-497d-af34-a6629a6cd700" azure_client_secret: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 33333338343036366530346164376430303132653031376264346232383330373730303633623461 - 6662636633373131333362623766346361383533303936340a323336626262653465303733363539 - 35333463623633663836303831663935346234356466626430346639623766383133336664346436 - 3038646535623434390a393965643933373536316264373264303561653063656662373962626136 - 31363866616161303761353436323864383965393763616565626162653936616638626166323738 - 6164633165343265323665336532303538613133653764366231 + $ANSIBLE_VAULT;1.1;AES256 + 66326536666564323330643461346239313738623566666633303934376132393739373862313463 + 6331336437353664366661316631626262643031343736300a313963366537353861633330646463 + 34396633643138313063326434323732343934383535636530646164323930356433666431396362 + 3638303539323239380a333865663734336261343066313361343435376163326661366630633463 + 31343965633836653436653465323438626262333565303034336237646665393661626362333766 + 3130326232336638373737356261616530373138666465653137 k8s_environment_variables: DATABASE_URL: "{{ env_database_url }}" @@ -211,7 +220,7 @@ k8s_install_descheduler: yes # You must set the k8s_descheduler_chart_version to match the Kubernetes # node version (0.23.x -> K8s 1.23.x); see: # https://github.com/kubernetes-sigs/descheduler#compatibility-matrix -k8s_descheduler_chart_version: v0.25.2 +k8s_descheduler_chart_version: v0.29.0 # See values.yaml for options: # https://github.com/kubernetes-sigs/descheduler/blob/master/charts/descheduler/values.yaml#L63 k8s_descheduler_release_values: diff --git a/deploy/host_vars/production.yml b/deploy/host_vars/production.yml index ae1de58d..d68abe14 100644 --- a/deploy/host_vars/production.yml +++ b/deploy/host_vars/production.yml @@ -67,16 +67,6 @@ database_password: !vault | 38653066646362323434626564333166653433663261643465356538386265353239336131633061 3236316135343961646363316531383661623336623564336637 -azure_client_id: "39459c9f-77b3-4fae-942c-7ef9fbf1332c" -azure_client_secret: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 62383037633362323633303630383031306666613239653333353735653662633265316635346135 - 3439326633363038333432666238396364386165383932320a653231323632393839313966306165 - 66633866646436366264336462636264383232386366636132343337363163343236306661316138 - 3664656261353362630a613966313138373630333935323863376565386364353733343566646639 - 38363430336432343538616536643963613434663734626565626563343764373231653561636364 - 6465343637323033386364356634356132353364336235663264 - env_django_secret_key: !vault | $ANSIBLE_VAULT;1.1;AES256 63323239653538366637613837333237633038383531396663616236663436363431306234623962 diff --git a/deploy/inventory b/deploy/inventory index baa6cafd..15793b0f 100644 --- a/deploy/inventory +++ b/deploy/inventory @@ -1,3 +1,5 @@ +runner ansible_host=100.29.176.240 + [k8s] dr staging diff --git a/deploy/requirements.yml b/deploy/requirements.yml index f8b2b98f..aae7660f 100644 --- a/deploy/requirements.yml +++ b/deploy/requirements.yml @@ -2,13 +2,16 @@ - src: https://github.com/caktus/ansible-role-aws-web-stacks name: caktus.aws-web-stacks - src: https://github.com/caktus/ansible-role-k8s-web-cluster - version: v1.5.0 + version: v1.6.0 name: caktus.k8s-web-cluster # Note: caktus.django-k8s version 1.4.0 has been released, but deploys fail due to issue: # msg: Failed to find exact match for rabbitmq.com/v1beta1.RabbitmqCluster by [kind, name, singularName, shortNames] - src: https://github.com/caktus/ansible-role-django-k8s name: caktus.django-k8s - version: v1.6.0 + version: v1.9.0 - src: https://github.com/caktus/ansible-role-k8s-hosting-services name: caktus.k8s-hosting-services - version: v0.11.0 + version: v0.12.0 + +- src: weareinteractive.users + version: 1.17.0 \ No newline at end of file diff --git a/deploy/stack/eks-no-nat.yml b/deploy/stack/eks-nat.yml similarity index 79% rename from deploy/stack/eks-no-nat.yml rename to deploy/stack/eks-nat.yml index b99c3d00..ca822ccf 100644 --- a/deploy/stack/eks-no-nat.yml +++ b/deploy/stack/eks-nat.yml @@ -1,8 +1,9 @@ # This Cloudformation stack template was generated by # https://github.com/caktus/aws-web-stacks -# at 2021-03-10 21:34:31.917371 +# at 2023-10-12 14:57:50.784099 # with parameters: # USE_EKS = on +# USE_NAT_GATEWAY = on Conditions: AssetsCloudFrontCertArnCondition: !Not @@ -31,6 +32,26 @@ Conditions: - !Equals - !Ref 'RedisAuthToken' - DO_NOT_CREATE_AUTH_TOKEN + BastionAMISet: !Not + - !Equals + - '' + - !Ref 'BastionAMI' + BastionDatabaseCondition: !And + - !Condition 'BastionTypeIsOpenVPNSet' + - !Condition 'DatabaseCondition' + BastionTypeAndAMISet: !And + - !Condition 'BastionTypeSet' + - !Condition 'BastionAMISet' + BastionTypeIsOpenVPNSet: !Equals + - OpenVPN + - !Ref 'BastionType' + BastionTypeIsSSHSet: !Equals + - SSH + - !Ref 'BastionType' + BastionTypeSet: !Not + - !Equals + - (none) + - !Ref 'BastionType' CmkArnCondition: !Not - !Equals - !Ref 'CustomerManagedCmkArn' @@ -57,6 +78,14 @@ Conditions: - !Equals - !Ref 'ElasticsearchInstanceType' - (none) + EnableEksEncryptionConfigCond: !And + - !Equals + - !Ref 'EnableEksEncryptionConfig' + - 'true' + - !Not + - !Equals + - !Ref 'CustomerManagedCmkArn' + - '' InGovCloudRegion: !Equals - !Ref 'AWS::Region' - us-gov-west-1 @@ -68,6 +97,12 @@ Conditions: RedisAutomaticFailoverCondition: !Equals - !Ref 'RedisAutomaticFailover' - 'true' + RestrictEksApiAccessCond: !Not + - !Equals + - !Join + - '' + - !Ref 'EksPublicAccessCidrs' + - '' SecureRedisCondition: !And - !Condition 'UsingRedis' - !Condition 'UseAES256EncryptionCond' @@ -193,6 +228,19 @@ Metadata: - RedisNumCacheClusters - RedisSnapshotRetentionLimit - RedisAutomaticFailover + - Label: + default: Elastic Kubernetes Service (EKS) + Parameters: + - EnableEksEncryptionConfig + - EksPublicAccessCidrs + - EksClusterName + - Label: + default: Bastion Server + Parameters: + - BastionType + - BastionAMI + - BastionInstanceType + - BastionKeyName ParameterLabels: AdministratorIPAddress: default: Admin IP Address @@ -204,6 +252,14 @@ Metadata: default: CloudFront Custom Domain AssetsUseCloudFront: default: Enable CloudFront + BastionAMI: + default: AMI + BastionInstanceType: + default: Instance Type + BastionKeyName: + default: SSH Key Name + BastionType: + default: Type CacheNodeType: default: Instance Type ContainerInstanceType: @@ -242,12 +298,18 @@ Metadata: default: Domain Name DomainNameAlternates: default: Alternate Domain Names + EksClusterName: + default: Cluster name + EksPublicAccessCidrs: + default: Kubernetes API public access CIDRs ElasticsearchInstanceType: default: Instance Type ElasticsearchVersion: default: Version ElasticsearchVolumeSize: default: Storage (GB) + EnableEksEncryptionConfig: + default: Enable EKS EncryptionConfig MaxScale: default: Maximum Instance Count PrimaryAZ: @@ -288,6 +350,10 @@ Outputs: Condition: AssetsUseCloudFrontCondition Description: The assets CDN domain name Value: !GetAtt 'AssetsDistribution.DomainName' + BastionIP: + Condition: BastionTypeSet + Description: Public IP address of Bastion instance + Value: !Ref 'BastionEIP' CacheAddress: Condition: UsingMemcached Description: The DNS address for the cache node/cluster. @@ -434,8 +500,7 @@ Outputs: Parameters: AdministratorIPAddress: Default: 192.0.2.0/24 - Description: The IP address allowed to access containers. Defaults to TEST-NET-1 - (ie, no valid IP) + Description: The IP address allowed to access containers. Defaults to TEST-NET-1 (ie, no valid IP) Type: String AssetsBucketAccessControl: AllowedValues: @@ -443,30 +508,140 @@ Parameters: - Private ConstraintDescription: Must be PublicRead or Private. Default: PublicRead - Description: Canned ACL for the public S3 bucket. Private is recommended; it allows - for objects to be make publicly readable, but prevents listing of the bucket - contents. + Description: Canned ACL for the public S3 bucket. Private is recommended; it allows for objects to be make publicly readable, but prevents listing of the bucket contents. Type: String AssetsCloudFrontCertArn: Default: '' Description: >- - If (1) you specified a custom static media domain, (2) your stack is NOT in - the us-east-1 region, and (3) you wish to serve static media over HTTPS, you - must manually create an ACM certificate in the us-east-1 region and provide - its ARN here. + If (1) you specified a custom static media domain, (2) your stack is NOT in the us-east-1 region, and (3) you wish to serve static media over HTTPS, you must manually create an ACM certificate in + the us-east-1 region and provide its ARN here. Type: String AssetsCloudFrontDomain: Default: '' - Description: A custom domain name (CNAME) for your CloudFront distribution, e.g., - "static.example.com". + Description: A custom domain name (CNAME) for your CloudFront distribution, e.g., "static.example.com". Type: String AssetsUseCloudFront: AllowedValues: - 'true' - 'false' Default: 'true' - Description: Whether or not to create a CloudFront distribution tied to the S3 - assets bucket. + Description: Whether or not to create a CloudFront distribution tied to the S3 assets bucket. + Type: String + BastionAMI: + Default: '' + Description: (Optional) Bastion or VPN server AMI in the same region as this stack. + Type: String + BastionInstanceType: + AllowedValues: + - t3.nano + - t3.micro + - t3.small + - t3.medium + - t3.large + - t3.xlarge + - t3.2xlarge + - t2.nano + - t2.micro + - t2.small + - t2.medium + - t2.large + - t2.xlarge + - t2.2xlarge + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.12xlarge + - m5.24xlarge + - m5d.large + - m5d.xlarge + - m5d.2xlarge + - m5d.4xlarge + - m5d.12xlarge + - m5d.24xlarge + - m4.large + - m4.xlarge + - m4.2xlarge + - m4.4xlarge + - m4.10xlarge + - m4.16xlarge + - m3.medium + - m3.large + - m3.xlarge + - m3.2xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.18xlarge + - c5d.large + - c5d.xlarge + - c5d.2xlarge + - c5d.4xlarge + - c5d.9xlarge + - c5d.18xlarge + - c4.large + - c4.xlarge + - c4.2xlarge + - c4.4xlarge + - c4.8xlarge + - c3.large + - c3.xlarge + - c3.2xlarge + - c3.4xlarge + - c3.8xlarge + - p2.xlarge + - p2.8xlarge + - p2.16xlarge + - g2.2xlarge + - g2.8xlarge + - x1.16large + - x1.32xlarge + - r5.large + - r5.xlarge + - r5.2xlarge + - r5.4xlarge + - r5.12xlarge + - r5.24xlarge + - r4.large + - r4.xlarge + - r4.2xlarge + - r4.4xlarge + - r4.8xlarge + - r4.16xlarge + - r3.large + - r3.xlarge + - r3.2xlarge + - r3.4xlarge + - r3.8xlarge + - i3.large + - i3.xlarge + - i3.2xlarge + - i3.4xlarge + - i3.8xlarge + - i3.16large + - d2.xlarge + - d2.2xlarge + - d2.4xlarge + - d2.8xlarge + - f1.2xlarge + - f1.16xlarge + Default: t2.nano + Description: (Optional) Instance type to use for bastion server. + Type: String + BastionKeyName: + ConstraintDescription: must be the name of an existing EC2 KeyPair. + Default: (none) + Description: Name of an existing EC2 KeyPair to enable SSH access to the Bastion instance. This parameter is required even if no Bastion AMI is specified (but will be unused). + Type: AWS::EC2::KeyPair::KeyName + BastionType: + AllowedValues: + - (none) + - SSH + - OpenVPN + Default: (none) + Description: Type of bastion server to create. Determines the default security group ingress rules to create. Type: String CacheNodeType: AllowedValues: @@ -661,8 +836,7 @@ Parameters: - '34' - '35' Default: '30' - Description: The number of days for which automated backups are retained. Setting - to 0 disables automated backups. + Description: The number of days for which automated backups are retained. Setting to 0 disables automated backups. Type: Number DatabaseClass: AllowedValues: @@ -690,6 +864,12 @@ Parameters: - db.t2.small - db.t2.medium - db.t2.large + - db.t4g.micro + - db.t4g.small + - db.t4g.medium + - db.t4g.large + - db.t4g.xlarge + - db.t4g.2xlarge - db.t3.micro - db.t3.small - db.t3.medium @@ -727,8 +907,7 @@ Parameters: Type: String DatabaseCloudWatchLogTypes: Default: '' - Description: A comma-separated list of the RDS log types (if any) to publish to - CloudWatch Logs. Note that log types are database engine-specific. + Description: A comma-separated list of the RDS log types (if any) to publish to CloudWatch Logs. Note that log types are database engine-specific. Type: CommaDelimitedList DatabaseEngine: AllowedValues: @@ -762,8 +941,7 @@ Parameters: Type: String DatabaseName: AllowedPattern: '[a-zA-Z][a-zA-Z0-9_]*' - ConstraintDescription: must begin with a letter and contain only alphanumeric - characters. + ConstraintDescription: must begin with a letter and contain only alphanumeric characters. Default: app Description: Name of the database to create in the database server MaxLength: '64' @@ -792,13 +970,12 @@ Parameters: - oracle-se2-12.1 - oracle-se2-12.2 - aurora5.6 - - postgres9.3 - - postgres9.4 - - postgres9.5 - - postgres9.6 - postgres10 - postgres11 - postgres12 + - postgres13 + - postgres14 + - postgres15 - sqlserver-ee-11.0 - sqlserver-ee-12.0 - sqlserver-ee-13.0 @@ -815,15 +992,12 @@ Parameters: - sqlserver-web-12.0 - sqlserver-web-13.0 - sqlserver-web-14.0 - Description: Database parameter group family name; must match the engine and version - of the RDS instance. + Description: Database parameter group family name; must match the engine and version of the RDS instance. Type: String DatabasePassword: AllowedPattern: '[ !#-.0-?A-~]*' - ConstraintDescription: must consist of 10-41 printable ASCII characters except - "/", """, or "@". - Description: The database admin account password must consist of 10-41 printableASCII - characters *except* "/", """, or "@". + ConstraintDescription: must consist of 10-41 printable ASCII characters except "/", """, or "@". + Description: The database admin account password must consist of 10-41 printableASCII characters *except* "/", """, or "@". MaxLength: '41' MinLength: '10' NoEcho: true @@ -833,13 +1007,11 @@ Parameters: - 'true' - 'false' Default: 'false' - Description: Whether to create a database server replica - WARNING this will fail - if DatabaseBackupRetentionDays is 0. + Description: Whether to create a database server replica - WARNING this will fail if DatabaseBackupRetentionDays is 0. Type: String DatabaseUser: AllowedPattern: '[a-zA-Z][a-zA-Z0-9_]*' - ConstraintDescription: must begin with a letter and contain only alphanumeric - characters and underscores. + ConstraintDescription: must begin with a letter and contain only alphanumeric characters and underscores. Default: app Description: The database admin account username MaxLength: '63' @@ -853,8 +1025,14 @@ Parameters: Description: The fully-qualified domain name for the application. Type: String DomainNameAlternates: - Description: A comma-separated list of Alternate FQDNs to be included in the Subject - Alternative Name extension of the SSL certificate. + Description: A comma-separated list of Alternate FQDNs to be included in the Subject Alternative Name extension of the SSL certificate. + Type: CommaDelimitedList + EksClusterName: + Description: The unique name to give to your cluster. + Type: String + EksPublicAccessCidrs: + Default: '' + Description: The CIDR blocks that are allowed access to your cluster's public Kubernetes API server endpoint. Type: CommaDelimitedList ElasticsearchInstanceType: AllowedValues: @@ -891,8 +1069,7 @@ Parameters: - i2.2xlarge.elasticsearch ConstraintDescription: must select a valid Elasticsearch instance type. Default: (none) - Description: 'Elasticsearch instance type. Note: not all types are supported in - all regions; see: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html' + Description: 'Elasticsearch instance type. Note: not all types are supported in all regions; see: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-supported-instance-types.html' Type: String ElasticsearchVersion: AllowedValues: @@ -902,16 +1079,21 @@ Parameters: - '5.3' ConstraintDescription: must select a valid Elasticsearch version. Default: '2.3' - Description: 'Elasticsearch version. Note: t2.micro.elasticsearch instances support - only versions 2.3 and 1.5.' + Description: 'Elasticsearch version. Note: t2.micro.elasticsearch instances support only versions 2.3 and 1.5.' Type: String ElasticsearchVolumeSize: Default: '10' - Description: 'Elasticsearch EBS volume size, in GB. Note: maximum volume size - varies by instance type; see: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html#ebsresource.' + Description: 'Elasticsearch EBS volume size, in GB. Note: maximum volume size varies by instance type; see: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html#ebsresource.' MaxValue: '1536' MinValue: '10' Type: Number + EnableEksEncryptionConfig: + AllowedValues: + - 'true' + - 'false' + Default: 'false' + Description: Use AWS Key Management Service (KMS) keys to provide envelope encryption of Kubernetes secrets. Depends on Customer managed key ARN. + Type: String MaxScale: Default: '4' Description: Maximum container instances count @@ -924,40 +1106,34 @@ Parameters: ^((10\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)|(172\.(1[6-9]|2[0-9]|3[0-1])\.)|192\.168\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: Must be a private IPv4 range with size /16 and /28. Default: 10.0.8.0/22 - Description: IPv4 CIDR block for the private subnet in the primary AZ. [Possibly - not modifiable after stack creation] + Description: IPv4 CIDR block for the private subnet in the primary AZ. [Possibly not modifiable after stack creation] Type: String PrivateSubnetBCidr: AllowedPattern: >- ^((10\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)|(172\.(1[6-9]|2[0-9]|3[0-1])\.)|192\.168\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: Must be a private IPv4 range with size /16 and /28. Default: 10.0.12.0/22 - Description: IPv4 CIDR block for the private subnet in the secondary AZ. [Possibly - not modifiable after stack creation] + Description: IPv4 CIDR block for the private subnet in the secondary AZ. [Possibly not modifiable after stack creation] Type: String PublicSubnetACidr: AllowedPattern: >- ^((10\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)|(172\.(1[6-9]|2[0-9]|3[0-1])\.)|192\.168\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: Must be a private IPv4 range with size /16 and /28. Default: 10.0.0.0/22 - Description: IPv4 CIDR block for the public subnet in the primary AZ. [Possibly - not modifiable after stack creation] + Description: IPv4 CIDR block for the public subnet in the primary AZ. [Possibly not modifiable after stack creation] Type: String PublicSubnetBCidr: AllowedPattern: >- ^((10\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)|(172\.(1[6-9]|2[0-9]|3[0-1])\.)|192\.168\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: Must be a private IPv4 range with size /16 and /28. Default: 10.0.4.0/22 - Description: IPv4 CIDR block for the public subnet in the secondary AZ. [Possibly - not modifiable after stack creation] + Description: IPv4 CIDR block for the public subnet in the secondary AZ. [Possibly not modifiable after stack creation] Type: String RedisAuthToken: AllowedPattern: '[ !#-.0-?A-~]*' - ConstraintDescription: must consist of 16-128 printable ASCII characters except - "/", """, or "@". + ConstraintDescription: must consist of 16-128 printable ASCII characters except "/", """, or "@". Default: DO_NOT_CREATE_AUTH_TOKEN - Description: The password used to access a Redis ReplicationGroup (required for - HIPAA). + Description: The password used to access a Redis ReplicationGroup (required for HIPAA). MaxLength: '128' MinLength: '16' NoEcho: true @@ -967,8 +1143,7 @@ Parameters: - 'true' - 'false' Default: 'false' - Description: Specifies whether a read-only replica is automatically promoted to - read/write primary if the existing primary fails. + Description: Specifies whether a read-only replica is automatically promoted to read/write primary if the existing primary fails. Type: String RedisNodeType: AllowedValues: @@ -1010,49 +1185,42 @@ Parameters: RedisSnapshotRetentionLimit: Default: '0' Description: >- - The number of days for which ElastiCache retains automatic snapshots before - deleting them.For example, if you set SnapshotRetentionLimit to 5, a snapshot - that was taken today is retained for 5 days before being deleted. 0 = automatic - backups are disabled for this cluster. + The number of days for which ElastiCache retains automatic snapshots before deleting them.For example, if you set SnapshotRetentionLimit to 5, a snapshot that was taken today is retained for 5 days + before being deleted. 0 = automatic backups are disabled for this cluster. Type: Number RedisVersion: Default: '' Description: 'Redis version to use. See available versions: aws elasticache describe-cache-engine-versions' Type: String SecondaryAZ: - Description: The secondary availability zone for creating resources. Must differ - from primary zone. + Description: The secondary availability zone for creating resources. Must differ from primary zone. Type: AWS::EC2::AvailabilityZone::Name UseAES256Encryption: AllowedValues: - 'true' - 'false' Default: 'false' - Description: Whether or not to use server side encryption for S3, EBS, and RDS. - When true, encryption is enabled for all resources. + Description: Whether or not to use server side encryption for S3, EBS, and RDS. When true, encryption is enabled for all resources. Type: String UseSFTPServer: AllowedValues: - 'true' - 'false' Default: 'false' - Description: Whether or not to set up an SFTP service. If 'true', this will set - up a transfer server and add an S3 bucket for its use, along with a role and - policies for use when adding users. + Description: Whether or not to set up an SFTP service. If 'true', this will set up a transfer server and add an S3 bucket for its use, along with a role and policies for use when adding users. Type: String VpcCidr: AllowedPattern: >- ^((10\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)|(172\.(1[6-9]|2[0-9]|3[0-1])\.)|192\.168\.)(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.)([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: Must be a private IPv4 range with size /16 and /28. Default: 10.0.0.0/20 - Description: The primary IPv4 CIDR block for the VPC. [Possibly not modifiable - after stack creation] + Description: The primary IPv4 CIDR block for the VPC. [Possibly not modifiable after stack creation] Type: String Resources: ApplicationRepository: Properties: ImageScanningConfiguration: - ScanOnPush: 'true' + ScanOnPush: true RepositoryPolicyText: Statement: - Action: @@ -1144,10 +1312,10 @@ Resources: - Origin - Access-Control-Request-Headers - Access-Control-Request-Method - QueryString: 'true' + QueryString: true TargetOriginId: Assets ViewerProtocolPolicy: allow-all - Enabled: 'true' + Enabled: true Origins: - DomainName: !GetAtt 'AssetsBucket.DomainName' Id: Assets @@ -1166,6 +1334,94 @@ Resources: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' Type: AWS::CloudFront::Distribution + BastionEIP: + Condition: BastionTypeSet + Properties: + Domain: vpc + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + Type: AWS::EC2::EIP + BastionEIPAssociation: + Condition: BastionTypeAndAMISet + Properties: + AllocationId: !GetAtt 'BastionEIP.AllocationId' + InstanceId: !Ref 'BastionInstance' + Type: AWS::EC2::EIPAssociation + BastionInstance: + Condition: BastionTypeAndAMISet + Properties: + BlockDeviceMappings: + - DeviceName: /dev/sda1 + Ebs: + Encrypted: !Ref 'UseAES256Encryption' + KmsKeyId: !If + - CmkArnCondition + - !Ref 'CustomerManagedCmkArn' + - !Ref 'AWS::NoValue' + VolumeSize: 8 + VolumeType: gp2 + ImageId: !Ref 'BastionAMI' + InstanceType: !Ref 'BastionInstanceType' + KeyName: !Ref 'BastionKeyName' + SecurityGroupIds: + - !Ref 'BastionSecurityGroup' + SubnetId: !Ref 'PublicSubnetA' + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + - Key: Name + Value: !Join + - '-' + - - !Ref 'AWS::StackName' + - bastion + - Key: aws-web-stacks:role + Value: bastion + Type: AWS::EC2::Instance + BastionSecurityGroup: + Condition: BastionTypeSet + Properties: + GroupDescription: Bastion security group. + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + - Key: Name + Value: !Join + - '-' + - - !Ref 'AWS::StackName' + - bastion + VpcId: !Ref 'Vpc' + Type: AWS::EC2::SecurityGroup + BastionSecurityGroupIngressHTTPS: + Condition: BastionTypeIsOpenVPNSet + Properties: + CidrIp: !Ref 'AdministratorIPAddress' + Description: Administrator HTTPS access. + FromPort: 443 + GroupId: !Ref 'BastionSecurityGroup' + IpProtocol: tcp + ToPort: 443 + Type: AWS::EC2::SecurityGroupIngress + BastionSecurityGroupIngressOpenVPN: + Condition: BastionTypeIsOpenVPNSet + Properties: + CidrIp: '0.0.0.0/0' + Description: OpenVPN Access. + FromPort: 1194 + GroupId: !Ref 'BastionSecurityGroup' + IpProtocol: udp + ToPort: 1194 + Type: AWS::EC2::SecurityGroupIngress + BastionSecurityGroupIngressSSH: + Condition: BastionTypeSet + Properties: + CidrIp: !Ref 'AdministratorIPAddress' + Description: Administrator SSH access. + FromPort: 22 + GroupId: !Ref 'BastionSecurityGroup' + IpProtocol: tcp + ToPort: 22 + Type: AWS::EC2::SecurityGroupIngress CacheCluster: Condition: UsingMemcached Properties: @@ -1235,6 +1491,9 @@ Resources: SubnetIds: - !Ref 'PrivateSubnetA' - !Ref 'PrivateSubnetB' + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' Type: AWS::ElastiCache::SubnetGroup ContainerInstanceProfile: Properties: @@ -1256,7 +1515,6 @@ Resources: - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy - - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy Path: / Policies: - PolicyDocument: @@ -1383,6 +1641,8 @@ Resources: - Action: - logs:Create* - logs:PutLogEvents + - logs:DescribeLogGroups + - logs:DescribeLogStreams Effect: Allow Resource: !Join - '' @@ -1400,7 +1660,36 @@ Resources: DeletionPolicy: Retain Properties: RetentionInDays: 365 + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' Type: AWS::Logs::LogGroup + ContainerSecurityGroupKubernetesBastionIngress: + Condition: BastionTypeSet + Properties: + Description: Kubernetes API endpoint + FromPort: 443 + GroupId: !GetAtt 'EksCluster.ClusterSecurityGroupId' + IpProtocol: tcp + SourceSecurityGroupId: !Ref 'BastionSecurityGroup' + ToPort: 443 + Type: AWS::EC2::SecurityGroupIngress + ContainerSecurityGroupOpenVPNIngress: + Condition: BastionTypeIsOpenVPNSet + Properties: + GroupId: !GetAtt 'EksCluster.ClusterSecurityGroupId' + IpProtocol: '-1' + SourceSecurityGroupId: !Ref 'BastionSecurityGroup' + Type: AWS::EC2::SecurityGroupIngress + ContainerSecurityGroupSSHBastionIngress: + Condition: BastionTypeIsSSHSet + Properties: + FromPort: 22 + GroupId: !GetAtt 'EksCluster.ClusterSecurityGroupId' + IpProtocol: tcp + SourceSecurityGroupId: !Ref 'BastionSecurityGroup' + ToPort: 22 + Type: AWS::EC2::SecurityGroupIngress DatabaseInstance: Condition: DatabaseCondition DeletionPolicy: Snapshot @@ -1489,6 +1778,22 @@ Resources: - rds VpcId: !Ref 'Vpc' Type: AWS::EC2::SecurityGroup + DatabaseSecurityGroupBastionIngress: + Condition: BastionDatabaseCondition + Properties: + Description: Bastion Access + FromPort: !FindInMap + - RdsEngineMap + - !Ref 'DatabaseEngine' + - Port + GroupId: !Ref 'DatabaseSecurityGroup' + IpProtocol: tcp + SourceSecurityGroupId: !Ref 'BastionSecurityGroup' + ToPort: !FindInMap + - RdsEngineMap + - !Ref 'DatabaseEngine' + - Port + Type: AWS::EC2::SecurityGroupIngress DatabaseSubnetGroup: Condition: DatabaseCondition Properties: @@ -1502,8 +1807,30 @@ Resources: Type: AWS::RDS::DBSubnetGroup EksCluster: Properties: - Name: !Sub '${AWS::StackName}-cluster' + EncryptionConfig: !If + - EnableEksEncryptionConfigCond + - - Provider: + KeyArn: !Ref 'CustomerManagedCmkArn' + Resources: + - secrets + - !Ref 'AWS::NoValue' + Logging: + ClusterLogging: + EnabledTypes: + - Type: api + - Type: audit + - Type: authenticator + Name: !Ref 'EksClusterName' ResourcesVpcConfig: + EndpointPrivateAccess: !If + - RestrictEksApiAccessCond + - true + - false + EndpointPublicAccess: true + PublicAccessCidrs: !If + - RestrictEksApiAccessCond + - !Ref 'EksPublicAccessCidrs' + - !Ref 'AWS::NoValue' SecurityGroupIds: - !Ref 'EksClusterSecurityGroup' SubnetIds: @@ -1512,6 +1839,9 @@ Resources: - !Ref 'PrivateSubnetA' - !Ref 'PrivateSubnetB' RoleArn: !GetAtt 'EksServiceRole.Arn' + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' Type: AWS::EKS::Cluster EksClusterSecurityGroup: Properties: @@ -1556,7 +1886,7 @@ Resources: AWS: - !GetAtt 'ContainerInstanceRole.Arn' EBSOptions: - EBSEnabled: 'true' + EBSEnabled: true VolumeSize: !Ref 'ElasticsearchVolumeSize' ElasticsearchClusterConfig: InstanceType: !Ref 'ElasticsearchInstanceType' @@ -1581,14 +1911,51 @@ Resources: - - !Ref 'AWS::StackName' - igw Type: AWS::EC2::InternetGateway + NatGateway: + Properties: + AllocationId: !GetAtt 'NatIp.AllocationId' + SubnetId: !Ref 'PublicSubnetA' + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + - Key: Name + Value: !Join + - '-' + - - !Ref 'AWS::StackName' + - nat + Type: AWS::EC2::NatGateway + NatGatewayRoute: + Properties: + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref 'NatGateway' + RouteTableId: !Ref 'NatGatewayRouteTable' + Type: AWS::EC2::Route + NatGatewayRouteTable: + Properties: + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + - Key: Name + Value: !Join + - '-' + - - !Ref 'AWS::StackName' + - private + VpcId: !Ref 'Vpc' + Type: AWS::EC2::RouteTable + NatIp: + Properties: + Domain: vpc + Tags: + - Key: aws-web-stacks:stack-name + Value: !Ref 'AWS::StackName' + Type: AWS::EC2::EIP Nodegroup: DependsOn: - EksCluster Properties: ClusterName: !Ref 'EksCluster' - DiskSize: !Ref 'ContainerVolumeSize' - InstanceTypes: - - !Ref 'ContainerInstanceType' + LaunchTemplate: + Id: !Ref 'NodegroupLaunchTemplate' NodeRole: !GetAtt 'ContainerInstanceRole.Arn' ScalingConfig: DesiredSize: !Ref 'DesiredScale' @@ -1600,6 +1967,25 @@ Resources: Tags: aws-web-stacks:stack-name: !Ref 'AWS::StackName' Type: AWS::EKS::Nodegroup + NodegroupLaunchTemplate: + Properties: + LaunchTemplateData: + BlockDeviceMappings: + - DeviceName: /dev/xvda + Ebs: + DeleteOnTermination: true + Encrypted: !Ref 'UseAES256Encryption' + KmsKeyId: !If + - CmkArnCondition + - !Ref 'CustomerManagedCmkArn' + - !Ref 'AWS::NoValue' + VolumeSize: !Ref 'ContainerVolumeSize' + VolumeType: gp2 + InstanceType: !Ref 'ContainerInstanceType' + MetadataOptions: + HttpPutResponseHopLimit: 3 + HttpTokens: required + Type: AWS::EC2::LaunchTemplate PrivateAssetsBucket: DeletionPolicy: Retain Properties: @@ -1640,10 +2026,10 @@ Resources: - ;https:// - !Ref 'DomainNameAlternates' PublicAccessBlockConfiguration: - BlockPublicAcls: 'true' - BlockPublicPolicy: 'true' - IgnorePublicAcls: 'true' - RestrictPublicBuckets: 'true' + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true Tags: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' @@ -1654,7 +2040,7 @@ Resources: Properties: AvailabilityZone: !Ref 'PrimaryAZ' CidrBlock: !Ref 'PrivateSubnetACidr' - MapPublicIpOnLaunch: 'true' + MapPublicIpOnLaunch: false Tags: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' @@ -1669,14 +2055,14 @@ Resources: Type: AWS::EC2::Subnet PrivateSubnetARouteTableAssociation: Properties: - RouteTableId: !Ref 'PublicRouteTable' + RouteTableId: !Ref 'NatGatewayRouteTable' SubnetId: !Ref 'PrivateSubnetA' Type: AWS::EC2::SubnetRouteTableAssociation PrivateSubnetB: Properties: AvailabilityZone: !Ref 'SecondaryAZ' CidrBlock: !Ref 'PrivateSubnetBCidr' - MapPublicIpOnLaunch: 'true' + MapPublicIpOnLaunch: false Tags: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' @@ -1691,7 +2077,7 @@ Resources: Type: AWS::EC2::Subnet PrivateSubnetBRouteTableAssociation: Properties: - RouteTableId: !Ref 'PublicRouteTable' + RouteTableId: !Ref 'NatGatewayRouteTable' SubnetId: !Ref 'PrivateSubnetB' Type: AWS::EC2::SubnetRouteTableAssociation PublicRoute: @@ -1833,10 +2219,10 @@ Resources: - ;https:// - !Ref 'DomainNameAlternates' PublicAccessBlockConfiguration: - BlockPublicAcls: 'true' - BlockPublicPolicy: 'true' - IgnorePublicAcls: 'true' - RestrictPublicBuckets: 'true' + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true Tags: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' @@ -2020,11 +2406,18 @@ Resources: - - !Ref 'AWS::StackName' - sftp Type: AWS::Transfer::Server + VPCS3Endpoint: + Properties: + RouteTableIds: + - !Ref 'NatGatewayRouteTable' + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' + VpcId: !Ref 'Vpc' + Type: AWS::EC2::VPCEndpoint Vpc: Properties: CidrBlock: !Ref 'VpcCidr' - EnableDnsHostnames: 'true' - EnableDnsSupport: 'true' + EnableDnsHostnames: true + EnableDnsSupport: true Tags: - Key: aws-web-stacks:stack-name Value: !Ref 'AWS::StackName' @@ -2034,3 +2427,4 @@ Resources: - - !Ref 'AWS::StackName' - vpc Type: AWS::EC2::VPC + diff --git a/docker-compose.yml b/docker-compose.yml index ada29890..a9a2bf12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,7 +39,7 @@ services: args: USER_UID: ${USER_UID:-1000} USER_GID: ${USER_GID:-1000} - command: ["sleep", "infinity"] + command: [ "sleep", "infinity" ] links: - db:db ports: diff --git a/requirements/base/base.in b/requirements/base/base.in index 99d819cd..fd3d0433 100644 --- a/requirements/base/base.in +++ b/requirements/base/base.in @@ -18,8 +18,8 @@ django-sass-processor django-phonenumber-field django-honeypot whitenoise -boto3==1.28.4 -botocore==1.31.4 +boto3==1.34.104 +botocore==1.34.104 libsass phonenumberslite pdfid @@ -27,10 +27,12 @@ pdfid social-auth-app-django # Postgres -psycopg2-binary +psycopg2-binary==2.9.9 # Wagtail wagtail wagtailfontawesome wagtailmenus + +cffi==1.17.0 \ No newline at end of file diff --git a/requirements/base/base.txt b/requirements/base/base.txt index e0ad15ad..aeecf329 100644 --- a/requirements/base/base.txt +++ b/requirements/base/base.txt @@ -10,17 +10,19 @@ asgiref==3.3.4 # via django beautifulsoup4==4.8.2 # via wagtail -boto3==1.28.4 +boto3==1.34.104 # via -r requirements/base/base.in -botocore==1.31.4 +botocore==1.34.104 # via # -r requirements/base/base.in # boto3 # s3transfer certifi==2020.12.5 # via requests -cffi==1.14.5 - # via cryptography +cffi==1.17.0 + # via + # -r requirements/base/base.in + # cryptography chardet==4.0.0 # via requests cryptography==3.4.7 @@ -122,7 +124,7 @@ phonenumberslite==8.12.18 # via -r requirements/base/base.in pillow==9.3.0 # via wagtail -psycopg2-binary==2.8.6 +psycopg2-binary==2.9.9 # via -r requirements/base/base.in pycparser==2.20 # via cffi @@ -152,7 +154,7 @@ requests-oauthlib==1.3.0 # via social-auth-core rjsmin==1.1.0 # via django-compressor -s3transfer==0.6.1 +s3transfer==0.10.1 # via boto3 six==1.15.0 # via diff --git a/requirements/dev/dev.in b/requirements/dev/dev.in index 651bcc2d..ca13b19b 100644 --- a/requirements/dev/dev.in +++ b/requirements/dev/dev.in @@ -2,22 +2,22 @@ -c ../base/base.txt pyyaml==6.0.1 -black==22.6.0 +black==24.4.2 isort ipython jupyterlab # deploy -ansible==7.6.0 +ansible==9.5.1 invoke-kubesae==0.1.0 # AWS tools -awscli==1.29.4 +awscli==1.32.104 awslogs Jinja2==3.0.3 -openshift==0.12 +openshift==0.13.2 kubernetes==12.0.0 -kubernetes-validate~=1.25.0 +kubernetes-validate~=1.29.1 pre-commit coverage diff --git a/requirements/dev/dev.txt b/requirements/dev/dev.txt index 132f09f2..61283d96 100644 --- a/requirements/dev/dev.txt +++ b/requirements/dev/dev.txt @@ -4,84 +4,92 @@ # # pip-compile --output-file=requirements/dev/dev.txt requirements/dev/dev.in # -ansible==7.6.0 +ansible==9.5.1 # via # -r requirements/dev/dev.in # invoke-kubesae -ansible-core==2.14.6 +ansible-core==2.16.6 # via ansible anyascii==0.1.7 # via # -c requirements/dev/../base/base.txt # wagtail -anyio==2.1.0 +anyio==3.7.1 # via jupyter-server -appnope==0.1.2 - # via -r requirements/dev/dev.in -argon2-cffi==20.1.0 - # via notebook +appnope==0.1.4 + # via + # -r requirements/dev/dev.in + # ipykernel +argon2-cffi==23.1.0 + # via + # jupyter-server + # nbclassic + # notebook +argon2-cffi-bindings==21.2.0 + # via argon2-cffi asgiref==3.3.4 # via # -c requirements/dev/../base/base.txt # django -async-generator==1.10 - # via nbclient -attrs==20.3.0 +asttokens==2.4.1 + # via stack-data +attrs==23.2.0 # via # jsonschema - # pytest -awscli==1.29.4 + # referencing +awscli==1.32.104 # via -r requirements/dev/dev.in awslogs==0.14.0 # via -r requirements/dev/dev.in -babel==2.9.0 +babel==2.15.0 # via jupyterlab-server -backcall==0.2.0 - # via ipython beautifulsoup4==4.8.2 # via # -c requirements/dev/../base/base.txt + # nbconvert # wagtail -black==22.6.0 +black==24.4.2 # via -r requirements/dev/dev.in -bleach==3.3.0 +bleach==6.1.0 # via nbconvert -boto3==1.28.4 +boto3==1.34.104 # via # -c requirements/dev/../base/base.txt # awslogs # invoke-kubesae -botocore==1.31.4 +botocore==1.34.104 # via # -c requirements/dev/../base/base.txt # awscli # boto3 # s3transfer -cachetools==4.2.1 +cachetools==5.3.3 # via google-auth certifi==2020.12.5 # via # -c requirements/dev/../base/base.txt # kubernetes # requests -cffi==1.14.5 +cffi==1.17.0 # via # -c requirements/dev/../base/base.txt - # argon2-cffi + # argon2-cffi-bindings # cryptography -cfgv==3.2.0 +cfgv==3.4.0 # via pre-commit chardet==4.0.0 # via # -c requirements/dev/../base/base.txt # requests -click==8.1.3 +click==8.1.7 # via black -colorama==0.4.3 +colorama==0.4.6 # via # awscli # invoke-kubesae -coverage==5.4 +comm==0.2.2 + # via ipykernel +coverage[toml]==7.5.1 # via # -r requirements/dev/dev.in # pytest-cov @@ -89,13 +97,15 @@ cryptography==3.4.7 # via # -c requirements/dev/../base/base.txt # ansible-core -decorator==4.4.2 +debugpy==1.8.1 + # via ipykernel +decorator==5.1.1 # via ipython defusedxml==0.7.1 # via # -c requirements/dev/../base/base.txt # nbconvert -distlib==0.3.6 +distlib==0.3.8 # via virtualenv django==3.2.23 # via @@ -108,7 +118,7 @@ django==3.2.23 # django-treebeard # djangorestframework # wagtail -django-debug-toolbar==3.7.0 +django-debug-toolbar==4.3.0 # via -r requirements/dev/dev.in django-filter==2.4.0 # via @@ -134,69 +144,82 @@ djangorestframework==3.12.2 # via # -c requirements/dev/../base/base.txt # wagtail -docutils==0.15.2 +docutils==0.16 # via awscli draftjs-exporter==2.1.7 # via # -c requirements/dev/../base/base.txt # wagtail -entrypoints==0.3 - # via nbconvert +entrypoints==0.4 + # via jupyter-client et-xmlfile==1.0.1 # via # -c requirements/dev/../base/base.txt # openpyxl -factory-boy==3.2.0 +exceptiongroup==1.2.1 + # via + # anyio + # ipython + # pytest +executing==2.0.1 + # via stack-data +factory-boy==3.3.0 # via # -r requirements/dev/dev.in # pytest-factoryboy # wagtail-factories -faker==15.3.2 +faker==25.2.0 # via factory-boy -filelock==3.8.1 +fastjsonschema==2.19.1 + # via nbformat +filelock==3.14.0 # via virtualenv -flake8==6.0.0 +flake8==7.0.0 # via -r requirements/dev/dev.in -google-auth==1.26.1 +google-auth==2.29.0 # via kubernetes html5lib==1.1 # via # -c requirements/dev/../base/base.txt # wagtail -identify==1.5.13 +identify==2.5.36 # via pre-commit idna==2.10 # via # -c requirements/dev/../base/base.txt # anyio # requests -importlib-metadata==5.0.0 +importlib-metadata==7.1.0 # via -r requirements/dev/dev.in +importlib-resources==6.4.0 + # via kubernetes-validate inflection==0.5.1 # via pytest-factoryboy -iniconfig==1.1.1 +iniconfig==2.0.0 # via pytest -invoke==1.5.0 +invoke==2.2.0 # via invoke-kubesae invoke-kubesae==0.1.0 # via -r requirements/dev/dev.in -ipykernel==5.4.3 - # via notebook -ipython==7.20.0 +ipykernel==6.29.4 + # via + # nbclassic + # notebook +ipython==8.24.0 # via # -r requirements/dev/dev.in # ipykernel # jupyterlab ipython-genutils==0.2.0 # via - # jupyter-server - # nbformat + # nbclassic # notebook - # traitlets -isort==5.7.0 +isort==5.13.2 # via -r requirements/dev/dev.in -jedi==0.18.0 - # via ipython +jedi==0.19.1 + # via + # ipython + # pudb jinja2==3.0.3 # via # -r requirements/dev/dev.in @@ -204,84 +227,107 @@ jinja2==3.0.3 # jupyter-server # jupyterlab # jupyterlab-server + # nbclassic # nbconvert # notebook - # openshift jmespath==0.10.0 # via # -c requirements/dev/../base/base.txt # awslogs # boto3 # botocore -json5==0.9.5 +json5==0.9.25 # via jupyterlab-server -jsonschema==3.2.0 +jsonschema==4.22.0 # via # jupyterlab-server # kubernetes-validate # nbformat -jupyter-client==6.1.11 +jsonschema-specifications==2023.12.1 + # via jsonschema +jupyter-client==7.2.0 # via # ipykernel # jupyter-server + # nbclassic # nbclient # notebook -jupyter-core==4.7.1 +jupyter-core==5.7.2 # via + # ipykernel # jupyter-client # jupyter-server # jupyterlab + # nbclassic + # nbclient # nbconvert # nbformat # notebook -jupyter-server==1.3.0 +jupyter-server==1.24.0 # via # jupyterlab # jupyterlab-server # nbclassic -jupyterlab==3.0.7 + # notebook-shim +jupyterlab==3.5.3 # via -r requirements/dev/dev.in -jupyterlab-pygments==0.1.2 +jupyterlab-pygments==0.3.0 # via nbconvert -jupyterlab-server==2.2.1 +jupyterlab-server==2.16.3 # via jupyterlab kubernetes==12.0.0 # via # -r requirements/dev/dev.in # openshift -kubernetes-validate==1.25.2 +kubernetes-validate==1.29.1 # via -r requirements/dev/dev.in l18n==2020.6.1 # via # -c requirements/dev/../base/base.txt # wagtail -markupsafe==2.1.1 - # via jinja2 +markupsafe==2.1.5 + # via + # jinja2 + # nbconvert +matplotlib-inline==0.1.7 + # via + # ipykernel + # ipython mccabe==0.7.0 # via flake8 -mistune==0.8.4 +mistune==3.0.2 # via nbconvert -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via black -nbclassic==0.2.6 - # via jupyterlab -nbclient==0.5.2 +nbclassic==1.0.0 + # via + # jupyterlab + # notebook +nbclient==0.10.0 # via nbconvert -nbconvert==6.0.7 +nbconvert==7.16.4 # via # jupyter-server + # nbclassic # notebook -nbformat==5.1.2 +nbformat==5.10.4 # via # jupyter-server + # nbclassic # nbclient # nbconvert # notebook -nest-asyncio==1.5.1 - # via nbclient -nodeenv==1.5.0 +nest-asyncio==1.6.0 + # via + # ipykernel + # jupyter-client + # nbclassic + # notebook +nodeenv==1.8.0 # via pre-commit -notebook==6.2.0 +notebook==6.5.7 + # via jupyterlab +notebook-shim==0.2.4 # via nbclassic oauthlib==3.1.0 # via @@ -291,89 +337,92 @@ openpyxl==3.1.2 # via # -c requirements/dev/../base/base.txt # wagtail -openshift==0.12.0 +openshift==0.13.2 # via -r requirements/dev/dev.in -packaging==20.9 +packaging==24.0 # via # ansible-core - # bleach + # black + # ipykernel + # jupyter-server # jupyterlab # jupyterlab-server + # kubernetes-validate + # nbconvert + # pudb # pytest -pandocfilters==1.4.3 + # pytest-factoryboy +pandocfilters==1.5.1 # via nbconvert -parso==0.8.1 +parso==0.8.4 # via jedi -pathspec==0.9.0 +pathspec==0.12.1 # via black -pexpect==4.8.0 - # via ipython -pickleshare==0.7.5 +pexpect==4.9.0 # via ipython pillow==9.3.0 # via # -c requirements/dev/../base/base.txt # wagtail -platformdirs==2.5.2 +platformdirs==4.2.2 # via # black + # jupyter-core # virtualenv -pluggy==0.13.1 +pluggy==1.5.0 # via pytest -pre-commit==2.20.0 +pre-commit==3.7.1 # via -r requirements/dev/dev.in -prometheus-client==0.9.0 +prometheus-client==0.20.0 # via # jupyter-server + # nbclassic # notebook -prompt-toolkit==3.0.16 +prompt-toolkit==3.0.43 # via ipython +psutil==5.9.8 + # via ipykernel ptyprocess==0.7.0 # via # pexpect # terminado -pudb==2020.1 +pudb==2024.1 # via -r requirements/dev/dev.in -py==1.10.0 - # via pytest -pyasn1==0.4.8 +pure-eval==0.2.2 + # via stack-data +pyasn1==0.6.0 # via # pyasn1-modules # rsa -pyasn1-modules==0.2.8 +pyasn1-modules==0.4.0 # via google-auth -pycodestyle==2.10.0 +pycodestyle==2.11.1 # via flake8 pycparser==2.20 # via # -c requirements/dev/../base/base.txt # cffi -pyflakes==3.0.1 +pyflakes==3.2.0 # via flake8 -pygments==2.8.0 +pygments==2.18.0 # via # ipython - # jupyterlab-pygments # nbconvert # pudb -pyparsing==2.4.7 - # via packaging -pyrsistent==0.17.3 - # via jsonschema -pytest==7.1.3 +pytest==8.2.1 # via # -r requirements/dev/dev.in # pytest-cov # pytest-django # pytest-factoryboy # pytest-mock -pytest-cov==2.11.1 +pytest-cov==5.0.0 # via -r requirements/dev/dev.in -pytest-django==4.1.0 +pytest-django==4.8.0 # via -r requirements/dev/dev.in -pytest-factoryboy==2.1.0 +pytest-factoryboy==2.7.0 # via -r requirements/dev/dev.in -pytest-mock==3.5.1 +pytest-mock==3.14.0 # via -r requirements/dev/dev.in python-dateutil==2.8.1 # via @@ -388,7 +437,6 @@ python-string-utils==1.0.0 pytz==2021.1 # via # -c requirements/dev/../base/base.txt - # babel # django # django-modelcluster # l18n @@ -401,11 +449,18 @@ pyyaml==6.0.1 # kubernetes # kubernetes-validate # pre-commit -pyzmq==22.0.3 +pyzmq==26.0.3 # via + # ipykernel # jupyter-client # jupyter-server + # nbclassic # notebook +referencing==0.35.1 + # via + # jsonschema + # jsonschema-specifications + # kubernetes-validate requests==2.25.1 # via # -c requirements/dev/../base/base.txt @@ -417,37 +472,37 @@ requests-oauthlib==1.3.0 # via # -c requirements/dev/../base/base.txt # kubernetes -resolvelib==0.5.4 +resolvelib==1.0.1 # via ansible-core -rsa==4.5 +rpds-py==0.18.1 + # via + # jsonschema + # referencing +rsa==4.7.2 # via # awscli # google-auth -ruamel-yaml==0.16.12 - # via openshift -s3transfer==0.6.1 +s3transfer==0.10.1 # via # -c requirements/dev/../base/base.txt # awscli # boto3 -send2trash==1.5.0 +send2trash==1.8.3 # via # jupyter-server + # nbclassic # notebook six==1.15.0 # via # -c requirements/dev/../base/base.txt - # argon2-cffi + # asttokens # bleach - # google-auth # html5lib - # jsonschema # kubernetes # l18n # openshift # python-dateutil - # websocket-client -sniffio==1.2.0 +sniffio==1.3.1 # via anyio soupsieve==2.2 # via @@ -458,73 +513,96 @@ sqlparse==0.4.1 # -c requirements/dev/../base/base.txt # django # django-debug-toolbar +stack-data==0.6.3 + # via ipython telepath==0.2 # via # -c requirements/dev/../base/base.txt # wagtail -termcolor==1.1.0 +termcolor==2.4.0 # via awslogs -terminado==0.9.2 +terminado==0.18.1 # via # jupyter-server + # nbclassic # notebook -testpath==0.4.4 +tinycss2==1.3.0 # via nbconvert -toml==0.10.2 - # via pre-commit tomli==2.0.1 # via # black + # coverage + # jupyterlab # pytest -tornado==6.1 +tornado==6.4 # via # ipykernel # jupyter-client # jupyter-server # jupyterlab + # nbclassic # notebook # terminado -traitlets==5.0.5 +traitlets==5.14.3 # via + # comm # ipykernel # ipython # jupyter-client # jupyter-core # jupyter-server + # matplotlib-inline + # nbclassic # nbclient # nbconvert # nbformat # notebook +typing-extensions==4.11.0 + # via + # black + # ipython + # kubernetes-validate + # pytest-factoryboy + # urwid urllib3==1.26.4 # via # -c requirements/dev/../base/base.txt # botocore # kubernetes # requests -urwid==2.1.2 +urwid==2.6.12 + # via + # pudb + # urwid-readline +urwid-readline==0.14 # via pudb -virtualenv==20.17.1 +virtualenv==20.26.2 # via pre-commit wagtail==4.2.2 # via # -c requirements/dev/../base/base.txt # wagtail-factories -wagtail-factories==2.0.1 +wagtail-factories==4.1.0 # via -r requirements/dev/dev.in -wcwidth==0.2.5 - # via prompt-toolkit +wcwidth==0.2.13 + # via + # prompt-toolkit + # urwid webencodings==0.5.1 # via # -c requirements/dev/../base/base.txt # bleach # html5lib -websocket-client==0.57.0 - # via kubernetes + # tinycss2 +websocket-client==1.8.0 + # via + # jupyter-server + # kubernetes willow==1.4 # via # -c requirements/dev/../base/base.txt # wagtail -zipp==3.4.0 +zipp==3.18.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: