From 94d674143c03e2da6107618d220bea5c2d667aca Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:22:46 +0000 Subject: [PATCH 001/181] fix: Move docker scripts from ftrs as check english work with that --- scripts/docker/Dockerfile.metadata | 9 +- scripts/docker/dgoss.sh | 139 ++++++++++++++++++++++++++++ scripts/docker/docker.lib.sh | 83 +++++++---------- scripts/docker/docker.mk | 7 +- scripts/docker/dockerfile-linter.sh | 10 +- scripts/docker/tests/Dockerfile | 3 - scripts/docker/tests/docker.test.sh | 14 +-- 7 files changed, 190 insertions(+), 75 deletions(-) create mode 100644 scripts/docker/dgoss.sh mode change 100755 => 100644 scripts/docker/docker.lib.sh delete mode 100644 scripts/docker/tests/Dockerfile diff --git a/scripts/docker/Dockerfile.metadata b/scripts/docker/Dockerfile.metadata index f54092e8..e551e5b2 100644 --- a/scripts/docker/Dockerfile.metadata +++ b/scripts/docker/Dockerfile.metadata @@ -1,4 +1,3 @@ - # === Metadata ================================================================= ARG IMAGE @@ -20,3 +19,11 @@ LABEL \ org.opencontainers.image.revision=$GIT_COMMIT_HASH \ org.opencontainers.image.created=$BUILD_DATE \ org.opencontainers.image.version=$BUILD_VERSION + +# Create a non-root user and switch to it +RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser +USER appuser + +# Add appropriate healthcheck (below is placeholder checking Python is available) +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python --version || exit 1 diff --git a/scripts/docker/dgoss.sh b/scripts/docker/dgoss.sh new file mode 100644 index 00000000..e573a48b --- /dev/null +++ b/scripts/docker/dgoss.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# shellcheck disable=SC2016,SC2154,SC2166 + +# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. + +# SEE: https://github.com/goss-org/goss/blob/master/extras/dgoss/dgoss + +set -e + +USAGE="USAGE: $(basename "$0") [run|edit] " +GOSS_FILES_PATH="${GOSS_FILES_PATH:-.}" + +# Container runtime +CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" + +info() { + echo -e "INFO: $*" >&2; +} +error() { + echo -e "ERROR: $*" >&2; + exit 1; +} + +cleanup() { + set +e + { kill "$log_pid" && wait "$log_pid"; } 2> /dev/null + if [ -n "$CONTAINER_LOG_OUTPUT" ]; then + cp "$tmp_dir/docker_output.log" "$CONTAINER_LOG_OUTPUT" + fi + rm -rf "$tmp_dir" + if [[ $id ]];then + info "Deleting container" + $CONTAINER_RUNTIME rm -vf "$id" > /dev/null + fi +} + +run(){ + # Copy in goss + cp "${GOSS_PATH}" "$tmp_dir/goss" + chmod 755 "$tmp_dir/goss" + [[ -e "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" "$tmp_dir/goss.yaml" && chmod 644 "$tmp_dir/goss.yaml" + [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]] && cp "${GOSS_FILES_PATH}/goss_wait.yaml" "$tmp_dir" && chmod 644 "$tmp_dir/goss_wait.yaml" + [[ -n "${GOSS_VARS}" ]] && [[ -e "${GOSS_FILES_PATH}/${GOSS_VARS}" ]] && cp "${GOSS_FILES_PATH}/${GOSS_VARS}" "$tmp_dir" && chmod 644 "$tmp_dir/${GOSS_VARS}" + + # Switch between mount or cp files strategy + GOSS_FILES_STRATEGY=${GOSS_FILES_STRATEGY:="mount"} + case "$GOSS_FILES_STRATEGY" in + mount) + info "Starting $CONTAINER_RUNTIME container" + if [ "$CONTAINER_RUNTIME" == "podman" -a $# == 2 ]; then + id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}" sleep infinity) + else + id=$($CONTAINER_RUNTIME run -d -v "$tmp_dir:/goss:z" "${@:2}") + fi + ;; + cp) + info "Creating $CONTAINER_RUNTIME container" + id=$($CONTAINER_RUNTIME create "${@:2}") + info "Copy goss files into container" + $CONTAINER_RUNTIME cp "$tmp_dir/." "$id:/goss" + info "Starting $CONTAINER_RUNTIME container" + $CONTAINER_RUNTIME start "$id" > /dev/null + ;; + *) error "Wrong goss files strategy used! Correct options are \"mount\" or \"cp\"." + esac + + $CONTAINER_RUNTIME logs -f "$id" > "$tmp_dir/docker_output.log" 2>&1 & + log_pid=$! + info "Container ID: ${id:0:8}" +} + +get_docker_file() { + local cid=$1 # Docker container ID + local src=$2 # Source file path (in the container) + local dst=$3 # Destination file path + + if $CONTAINER_RUNTIME exec "${cid}" sh -c "test -e ${src}" > /dev/null; then + mkdir -p "${GOSS_FILES_PATH}" + $CONTAINER_RUNTIME cp "${cid}:${src}" "${dst}" + info "Copied '${src}' from container to '${dst}'" + fi +} + +# Main +tmp_dir=$(mktemp -d /tmp/tmp.XXXXXXXXXX) +chmod 777 "$tmp_dir" +trap 'ret=$?;cleanup;exit $ret' EXIT + +GOSS_PATH="${GOSS_PATH:-$(which goss 2> /dev/null || true)}" +[[ $GOSS_PATH ]] || { error "Couldn't find goss installation, please set GOSS_PATH to it"; } +[[ ${GOSS_OPTS+x} ]] || GOSS_OPTS="--color --format documentation" +[[ ${GOSS_WAIT_OPTS+x} ]] || GOSS_WAIT_OPTS="-r 30s -s 1s > /dev/null" +GOSS_SLEEP=${GOSS_SLEEP:-0.2} + +[[ $CONTAINER_RUNTIME =~ ^(docker|podman)$ ]] || { error "Runtime must be one of docker or podman"; } + +case "$1" in + run) + run "$@" + if [[ -e "${GOSS_FILES_PATH}/goss_wait.yaml" ]]; then + info "Found goss_wait.yaml, waiting for it to pass before running tests" + if [[ -z "${GOSS_VARS}" ]]; then + if ! $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss_wait.yaml validate $GOSS_WAIT_OPTS"; then + $CONTAINER_RUNTIME logs "$id" >&2 + error "goss_wait.yaml never passed" + fi + else + if ! $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss_wait.yaml --vars='/goss/${GOSS_VARS}' validate $GOSS_WAIT_OPTS"; then + $CONTAINER_RUNTIME logs "$id" >&2 + error "goss_wait.yaml never passed" + fi + fi + fi + [[ $GOSS_SLEEP ]] && { info "Sleeping for $GOSS_SLEEP"; sleep "$GOSS_SLEEP"; } + info "Container health" + if [ "true" != "$($CONTAINER_RUNTIME inspect -f '{{.State.Running}}' "$id")" ]; then + $CONTAINER_RUNTIME logs "$id" >&2 + error "the container failed to start" + fi + info "Running Tests" + if [[ -z "${GOSS_VARS}" ]]; then + $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss.yaml validate $GOSS_OPTS" + else + $CONTAINER_RUNTIME exec "$id" sh -c "/goss/goss -g /goss/goss.yaml --vars='/goss/${GOSS_VARS}' validate $GOSS_OPTS" + fi + ;; + edit) + run "$@" + info "Run goss add/autoadd to add resources" + $CONTAINER_RUNTIME exec -it "$id" sh -c 'cd /goss; PATH="/goss:$PATH" exec sh' + get_docker_file "$id" "/goss/goss.yaml" "${GOSS_FILES_PATH}/${GOSS_FILE:-goss.yaml}" + get_docker_file "$id" "/goss/goss_wait.yaml" "${GOSS_FILES_PATH}/goss_wait.yaml" + if [[ -n "${GOSS_VARS}" ]]; then + get_docker_file "$id" "/goss/${GOSS_VARS}" "${GOSS_FILES_PATH}/${GOSS_VARS}" + fi + ;; + *) + error "$USAGE" +esac diff --git a/scripts/docker/docker.lib.sh b/scripts/docker/docker.lib.sh old mode 100755 new mode 100644 index 5522a726..7c3cc012 --- a/scripts/docker/docker.lib.sh +++ b/scripts/docker/docker.lib.sh @@ -24,13 +24,15 @@ set -euo pipefail function docker-build() { local dir=${dir:-$PWD} + # establish if we are using docker or podman + DOCKER_CMD=$(_set_docker_cmd) version-create-effective-file _create-effective-dockerfile tag=$(_get-effective-tag) - docker build \ + $DOCKER_CMD build \ --progress=plain \ --platform linux/amd64 \ --build-arg IMAGE="${DOCKER_IMAGE}" \ @@ -49,12 +51,10 @@ function docker-build() { # Tag the image with all the stated versions, see the documentation for more details for version in $(_get-all-effective-versions) latest; do - if [[ ! -z "$version" ]]; then - docker tag "${tag}" "${DOCKER_IMAGE}:${version}" + if [ ! -z "$version" ]; then + $DOCKER_CMD tag "${tag}" "${DOCKER_IMAGE}:${version}" fi done - - return } # Create the Dockerfile.effective file to bake in version info @@ -66,8 +66,6 @@ function docker-bake-dockerfile() { version-create-effective-file _create-effective-dockerfile - - return } # Run hadolint over the generated Dockerfile. @@ -76,7 +74,6 @@ function docker-bake-dockerfile() { function docker-lint() { local dir=${dir:-$PWD} file=${dir}/Dockerfile.effective ./scripts/docker/dockerfile-linter.sh - return } # Check test Docker image. @@ -90,13 +87,11 @@ function docker-check-test() { local dir=${dir:-$PWD} # shellcheck disable=SC2086,SC2154 - docker run --rm --platform linux/amd64 \ + $DOCKER_CMD run --rm --platform linux/amd64 \ ${args:-} \ "${DOCKER_IMAGE}:$(_get-effective-version)" 2>/dev/null \ ${cmd:-} \ | grep -q "${check}" && echo PASS || echo FAIL - - return } # Run Docker image. @@ -110,12 +105,10 @@ function docker-run() { local tag=$(dir="$dir" _get-effective-tag) # shellcheck disable=SC2086 - docker run --rm --platform linux/amd64 \ + $DOCKER_CMD run --rm --platform linux/amd64 \ ${args:-} \ "${tag}" \ ${DOCKER_CMD:-} - - return } # Push Docker image. @@ -127,10 +120,8 @@ function docker-push() { # Push all the image tags based on the stated versions, see the documentation for more details for version in $(dir="$dir" _get-all-effective-versions) latest; do - docker push "${DOCKER_IMAGE}:${version}" + $DOCKER_CMD push "${DOCKER_IMAGE}:${version}" done - - return } # Remove Docker resources. @@ -141,14 +132,12 @@ function docker-clean() { local dir=${dir:-$PWD} for version in $(dir="$dir" _get-all-effective-versions) latest; do - docker rmi "${DOCKER_IMAGE}:${version}" > /dev/null 2>&1 ||: + $DOCKER_CMD rmi "${DOCKER_IMAGE}:${version}" > /dev/null 2>&1 ||: done rm -f \ .version \ Dockerfile.effective \ Dockerfile.effective.dockerignore - - return } # Create effective version from the VERSION file. @@ -161,7 +150,7 @@ function version-create-effective-file() { local version_file="$dir/VERSION" local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')} - if [[ -f "$version_file" ]]; then + if [ -f "$version_file" ]; then # shellcheck disable=SC2002 cat "$version_file" | \ sed "s/\(\${yyyy}\|\$yyyy\)/$(date --date="${build_datetime}" -u +"%Y")/g" | \ @@ -173,8 +162,6 @@ function version-create-effective-file() { sed "s/\(\${hash}\|\$hash\)/$(git rev-parse --short HEAD)/g" \ > "$dir/.version" fi - - return } # ============================================================================== @@ -203,9 +190,9 @@ function docker-get-image-version-and-pull() { # match it by name and version regex, if given. local versions_file="${TOOL_VERSIONS:=$(git rev-parse --show-toplevel)/.tool-versions}" local version="latest" - if [[ -f "$versions_file" ]]; then + if [ -f "$versions_file" ]; then line=$(grep "docker/${name} " "$versions_file" | sed "s/^#\s*//; s/\s*#.*$//" | grep "${match_version:-".*"}") - [[ -n "$line" ]] && version=$(echo "$line" | awk '{print $2}') + [ -n "$line" ] && version=$(echo "$line" | awk '{print $2}') fi # Split the image version into two, tag name and digest sha256. @@ -213,17 +200,17 @@ function docker-get-image-version-and-pull() { local digest="$(echo "$version" | sed 's/^.*@//')" # Check if the image exists locally already - if ! docker images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then - if [[ "$digest" != "latest" ]]; then + if ! $DOCKER_CMD images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then + if [ "$digest" != "latest" ]; then # Pull image by the digest sha256 and tag it - docker pull \ + $DOCKER_CMD pull \ --platform linux/amd64 \ "${name}@${digest}" \ > /dev/null 2>&1 || true - docker tag "${name}@${digest}" "${name}:${tag}" + $DOCKER_CMD tag "${name}@${digest}" "${name}:${tag}" else # Pull the latest image - docker pull \ + $DOCKER_CMD pull \ --platform linux/amd64 \ "${name}:latest" \ > /dev/null 2>&1 || true @@ -231,8 +218,6 @@ function docker-get-image-version-and-pull() { fi echo "${name}:${version}" - - return } # ============================================================================== @@ -249,14 +234,12 @@ function _create-effective-dockerfile() { # Dockerfile.effective file, otherwise docker won't use it. # See https://docs.docker.com/build/building/context/#filename-and-location # If using podman, this requires v5.0.0 or later. - if [[ -f "${dir}/Dockerfile.dockerignore" ]]; then + if [ -f "${dir}/Dockerfile.dockerignore" ]; then cp "${dir}/Dockerfile.dockerignore" "${dir}/Dockerfile.effective.dockerignore" fi cp "${dir}/Dockerfile" "${dir}/Dockerfile.effective" _replace-image-latest-by-specific-version _append-metadata - - return } # Replace image:latest by a specific version. @@ -269,7 +252,7 @@ function _replace-image-latest-by-specific-version() { local dockerfile="${dir}/Dockerfile.effective" local build_datetime=${BUILD_DATETIME:-$(date -u +'%Y-%m-%dT%H:%M:%S%z')} - if [[ -f "$versions_file" ]]; then + if [ -f "$versions_file" ]; then # First, list the entries specific for Docker to take precedence, then the rest but exclude comments content=$(grep " docker/" "$versions_file"; grep -v " docker/" "$versions_file" ||: | grep -v "^#") echo "$content" | while IFS= read -r line; do @@ -281,7 +264,7 @@ function _replace-image-latest-by-specific-version() { done fi - if [[ -f "$dockerfile" ]]; then + if [ -f "$dockerfile" ]; then # shellcheck disable=SC2002 cat "$dockerfile" | \ sed "s/\(\${yyyy}\|\$yyyy\)/$(date --date="${build_datetime}" -u +"%Y")/g" | \ @@ -297,8 +280,6 @@ function _replace-image-latest-by-specific-version() { # Do not ignore the issue if 'latest' is used in the effective image sed -Ei "/# hadolint ignore=DL3007$/d" "${dir}/Dockerfile.effective" - - return } # Append metadata to the end of Dockerfile. @@ -313,8 +294,6 @@ function _append-metadata() { "$(git rev-parse --show-toplevel)/scripts/docker/Dockerfile.metadata" \ > "$dir/Dockerfile.effective.tmp" mv "$dir/Dockerfile.effective.tmp" "$dir/Dockerfile.effective" - - return } # Print top Docker image version. @@ -325,8 +304,6 @@ function _get-effective-version() { local dir=${dir:-$PWD} head -n 1 "${dir}/.version" 2> /dev/null ||: - - return } # Print the effective tag for the image with the version. If you don't have a VERSION file @@ -337,12 +314,10 @@ function _get-effective-tag() { local tag=$DOCKER_IMAGE version=$(_get-effective-version) - if [[ ! -z "$version" ]]; then + if [ ! -z "$version" ]; then tag="${tag}:${version}" fi echo "$tag" - - return } # Print all Docker image versions. @@ -353,8 +328,6 @@ function _get-all-effective-versions() { local dir=${dir:-$PWD} cat "${dir}/.version" 2> /dev/null ||: - - return } # Print Git branch name. Check the GitHub variables first and then the local Git @@ -363,14 +336,22 @@ function _get-git-branch-name() { local branch_name=$(git rev-parse --abbrev-ref HEAD) - if [[ -n "${GITHUB_HEAD_REF:-}" ]]; then + if [ -n "${GITHUB_HEAD_REF:-}" ]; then branch_name=$GITHUB_HEAD_REF - elif [[ -n "${GITHUB_REF:-}" ]]; then + elif [ -n "${GITHUB_REF:-}" ]; then # shellcheck disable=SC2001 branch_name=$(echo "$GITHUB_REF" | sed "s#refs/heads/##") fi echo "$branch_name" +} + +function get-docker-version() { + DOCKER_CMD=$(_set_docker_cmd) + $DOCKER_CMD -v +} - return +function _set_docker_cmd() { + DOCKER_CMD=$(command -v docker >/dev/null 2>&1 && echo docker || echo podman) + echo "$DOCKER_CMD" } diff --git a/scripts/docker/docker.mk b/scripts/docker/docker.mk index 16104c55..afa8bca5 100644 --- a/scripts/docker/docker.mk +++ b/scripts/docker/docker.mk @@ -35,10 +35,9 @@ clean:: # Remove Docker resources (docker) - optional: docker_dir|dir=[path to t _docker: # Docker command wrapper - mandatory: cmd=[command to execute]; optional: dir=[path to the image directory where the Dockerfile is located, relative to the project's top-level directory, default is '.'] # 'DOCKER_IMAGE' and 'DOCKER_TITLE' are passed to the functions as environment variables - # dir=$(realpath $(or ${dir}, infrastructure/images/${DOCKER_IMAGE})) - # source scripts/docker/docker.lib.sh - # docker-${cmd} # 'dir' is accessible by the function as environment variable - echo Docker images not used yet! + dir=$(realpath $(or ${dir}, infrastructure/images/${DOCKER_IMAGE})) + source scripts/docker/docker.lib.sh + docker-${cmd} # 'dir' is accessible by the function as environment variable # ============================================================================== # Quality checks - please DO NOT edit this section! diff --git a/scripts/docker/dockerfile-linter.sh b/scripts/docker/dockerfile-linter.sh index 061212ec..02ff6acf 100755 --- a/scripts/docker/dockerfile-linter.sh +++ b/scripts/docker/dockerfile-linter.sh @@ -27,8 +27,6 @@ function main() { else file="$file" run-hadolint-in-docker fi - - return } # Run hadolint natively. @@ -38,7 +36,6 @@ function run-hadolint-natively() { # shellcheck disable=SC2001 hadolint "$(echo "$file" | sed "s#$PWD#.#")" - return } # Run hadolint in a Docker container. @@ -48,19 +45,18 @@ function run-hadolint-in-docker() { # shellcheck disable=SC1091 source ./scripts/docker/docker.lib.sh - + DOCKER_CMD=$(_set_docker_cmd) + echo run in "$DOCKER_CMD" # shellcheck disable=SC2155 local image=$(name=hadolint/hadolint docker-get-image-version-and-pull) # shellcheck disable=SC2001 - docker run --rm --platform linux/amd64 \ + $DOCKER_CMD run --rm --platform linux/amd64 \ --volume "$PWD:/workdir" \ --workdir /workdir \ "$image" \ hadolint \ --config /workdir/scripts/config/hadolint.yaml \ "/workdir/$(echo "$file" | sed "s#$PWD#.#")" - - return } # ============================================================================== diff --git a/scripts/docker/tests/Dockerfile b/scripts/docker/tests/Dockerfile deleted file mode 100644 index b5ea5606..00000000 --- a/scripts/docker/tests/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -# `*:latest` will be replaced with a corresponding version stored in the '.tool-versions' file -# hadolint ignore=DL3007 -FROM python:latest diff --git a/scripts/docker/tests/docker.test.sh b/scripts/docker/tests/docker.test.sh index 1092179d..8f487b8f 100755 --- a/scripts/docker/tests/docker.test.sh +++ b/scripts/docker/tests/docker.test.sh @@ -44,17 +44,19 @@ function main() { done echo "Total: ${#tests[@]}, Passed: $(( ${#tests[@]} - status )), Failed: $status" test-docker-suite-teardown - [[ $status -gt 0 ]] && return 1 || return 0 + [ $status -gt 0 ] && return 1 || return 0 } # ============================================================================== function test-docker-suite-setup() { - return + + : } function test-docker-suite-teardown() { - return + + : } # ============================================================================== @@ -104,8 +106,6 @@ function test-docker-test() { output=$(docker-check-test) # Assert echo "$output" | grep -q "PASS" - - return } function test-docker-run() { @@ -116,8 +116,6 @@ function test-docker-run() { output=$(docker-run) # Assert echo "$output" | grep -Eq "Python [0-9]+\.[0-9]+\.[0-9]+" - - return } function test-docker-clean() { @@ -142,8 +140,6 @@ function test-docker-get-image-version-and-pull() { --filter=reference="$name" \ --format "{{.Tag}}" \ | grep -vq "" - - return } # ============================================================================== From ab2a4188af7218001b285a9e18f83ab35aeec345 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:32:27 +0000 Subject: [PATCH 002/181] fix: Minor change --- scripts/docker/docker.lib.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/docker/docker.lib.sh b/scripts/docker/docker.lib.sh index 7c3cc012..9276cf3b 100644 --- a/scripts/docker/docker.lib.sh +++ b/scripts/docker/docker.lib.sh @@ -85,6 +85,7 @@ function docker-lint() { function docker-check-test() { local dir=${dir:-$PWD} + DOCKER_CMD=$(_set_docker_cmd) # shellcheck disable=SC2086,SC2154 $DOCKER_CMD run --rm --platform linux/amd64 \ @@ -102,6 +103,7 @@ function docker-check-test() { function docker-run() { local dir=${dir:-$PWD} + DOCKER_CMD=$(_set_docker_cmd) local tag=$(dir="$dir" _get-effective-tag) # shellcheck disable=SC2086 @@ -117,6 +119,7 @@ function docker-run() { function docker-push() { local dir=${dir:-$PWD} + DOCKER_CMD=$(_set_docker_cmd) # Push all the image tags based on the stated versions, see the documentation for more details for version in $(dir="$dir" _get-all-effective-versions) latest; do @@ -130,6 +133,7 @@ function docker-push() { function docker-clean() { local dir=${dir:-$PWD} + DOCKER_CMD=$(_set_docker_cmd) for version in $(dir="$dir" _get-all-effective-versions) latest; do $DOCKER_CMD rmi "${DOCKER_IMAGE}:${version}" > /dev/null 2>&1 ||: @@ -198,6 +202,7 @@ function docker-get-image-version-and-pull() { # Split the image version into two, tag name and digest sha256. local tag="$(echo "$version" | sed 's/@.*$//')" local digest="$(echo "$version" | sed 's/^.*@//')" + DOCKER_CMD=$(_set_docker_cmd) # Check if the image exists locally already if ! $DOCKER_CMD images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then From 56764d8654197adad2a1c37486354594db2449b0 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:36:10 +0000 Subject: [PATCH 003/181] fix: Minor changes to script --- scripts/workflow/action-infra-stack.sh | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/scripts/workflow/action-infra-stack.sh b/scripts/workflow/action-infra-stack.sh index 73f9bd89..0b4de960 100644 --- a/scripts/workflow/action-infra-stack.sh +++ b/scripts/workflow/action-infra-stack.sh @@ -51,17 +51,17 @@ export TF_VAR_terraform_lock_table_name="nhse-$ENVIRONMENT-$TF_VAR_repo_name-ter # check exports have been done EXPORTS_SET=0 # Check key variables have been exported - see above -if [[ -z "$ACTION" ]] ; then +if [ -z "$ACTION" ] ; then echo Set ACTION to terraform action one of plan, apply, destroy, or validate EXPORTS_SET=1 fi -if [[ -z "$STACK" ]] ; then +if [ -z "$STACK" ] ; then echo Set STACK to name of the stack to be actioned EXPORTS_SET=1 fi -if [[ -z "$ENVIRONMENT" ]] ; then +if [ -z "$ENVIRONMENT" ] ; then echo Set ENVIRONMENT to the environment to action the terraform in - one of dev, test, preprod, prod EXPORTS_SET=1 else @@ -71,7 +71,7 @@ else fi fi -# if [[ -z "$PROJECT" ]] ; then +# if [ -z "$PROJECT" ] ; then # echo Set PROJECT to dos or cm # EXPORTS_SET=1 # else @@ -81,17 +81,17 @@ fi # fi # fi -if [[ -z "$WORKSPACE" ]] ; then +if [ -z "$WORKSPACE" ] ; then echo Set WORKSPACE to the workspace to action the terraform in EXPORTS_SET=1 fi -if [[ -z "$TF_VAR_repo_name" ]] ; then +if [ -z "$TF_VAR_repo_name" ] ; then echo Set REPOSITORY to the REPOSITORY to action the terraform in EXPORTS_SET=1 fi -if [[ $EXPORTS_SET = 1 ]] ; then +if [ $EXPORTS_SET = 1 ] ; then echo One or more exports not set exit 1 fi @@ -138,7 +138,7 @@ fi cd "$STACK_DIR" || exit # if no stack-specific tfvars create temporary one TEMP_STACK_TF_VARS_FILE=0 -if [[ ! -f "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" ]] ; then +if [ ! -f "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" ] ; then touch "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" TEMP_STACK_TF_VARS_FILE=1 fi @@ -155,7 +155,7 @@ fi # if no env specific tfvars for stack create temporary one TEMP_ENV_STACK_TF_VARS_FILE=0 -if [[ ! -f "$ENVIRONMENTS_DIR/$STACK_TF_VARS_FILE" ]] ; then +if [ ! -f "$ENVIRONMENTS_DIR/$STACK_TF_VARS_FILE" ] ; then touch "$ENVIRONMENTS_DIR/$STACK_TF_VARS_FILE" TEMP_ENV_STACK_TF_VARS_FILE=1 fi @@ -165,7 +165,7 @@ terraform-initialise terraform workspace select -or-create "$WORKSPACE" # plan -if [[ -n "$ACTION" ] && [ "$ACTION" = 'plan' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'plan' ] ; then terraform plan -out $STACK.tfplan \ -var-file "$ROOT_DIR/$INFRASTRUCTURE_DIR/$COMMON_TF_VARS_FILE" \ -var-file "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" \ @@ -174,7 +174,7 @@ if [[ -n "$ACTION" ] && [ "$ACTION" = 'plan' ]] ; then PLAN_RESULT=$(terraform show -no-color $STACK.tfplan) - if [[ -n "$GITHUB_WORKSPACE" ]] ; then + if [ -n "$GITHUB_WORKSPACE" ] ; then cp "$STACK.tfplan" "$GITHUB_WORKSPACE/$STACK.tfplan" # Look for the "No changes" string in the output for GitHub workflow. @@ -188,15 +188,15 @@ if [[ -n "$ACTION" ] && [ "$ACTION" = 'plan' ]] ; then fi fi -if [[ -n "$ACTION" ] && [ "$ACTION" = 'apply' ]] ; then - if [[ -n "$GITHUB_WORKSPACE" ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'apply' ] ; then + if [ -n "$GITHUB_WORKSPACE" ] ; then terraform apply -auto-approve "$GITHUB_WORKSPACE/$STACK.tfplan" else terraform apply -auto-approve "$STACK.tfplan" fi fi -if [[ -n "$ACTION" ] && [ "$ACTION" = 'destroy' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'destroy' ] ; then terraform destroy -auto-approve \ -var-file "$ROOT_DIR/$INFRASTRUCTURE_DIR/$COMMON_TF_VARS_FILE" \ -var-file "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" \ @@ -204,7 +204,7 @@ if [[ -n "$ACTION" ] && [ "$ACTION" = 'destroy' ]] ; then -var-file "$ENVIRONMENTS_DIR/$STACK_TF_VARS_FILE" fi -if [[ -n "$ACTION" ] && [ "$ACTION" = 'validate' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'validate' ] ; then terraform validate fi @@ -214,11 +214,11 @@ rm -f "$STACK_DIR"/provider.tf rm -f "$STACK_DIR"/versions.tf rm -f "$STACK_DIR"/common-variables.tf -if [[ $TEMP_STACK_TF_VARS_FILE = 1 ]] ; then +if [ $TEMP_STACK_TF_VARS_FILE = 1 ] ; then rm -f "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" fi -if [[ $TEMP_ENV_STACK_TF_VARS_FILE = 1 ]] ; then +if [ $TEMP_ENV_STACK_TF_VARS_FILE = 1 ] ; then rm -f "$ENVIRONMENTS_DIR/$STACK_TF_VARS_FILE" fi From 2a9031b1c9fa17a98179ce538afbaf6e9846cd15 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:47:41 +0000 Subject: [PATCH 004/181] fix: Add tfvars such that they are not ignored --- infrastructure/.gitignore | 4 ++++ infrastructure/common.tfvars | 14 ++++++++++++++ infrastructure/environments/dev/environment.tfvars | 3 +++ 3 files changed, 21 insertions(+) create mode 100644 infrastructure/common.tfvars create mode 100644 infrastructure/environments/dev/environment.tfvars diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index 22ebdac3..670f1db9 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -18,6 +18,10 @@ crash.*.log *.tfvars *.tfvars.json +# Exception: Allow specific tfvars files to be committed +!common.tfvars +!environment.tfvars + # Ignore override files as they are usually used to override resources locally and so # are not checked in override.tf diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars new file mode 100644 index 00000000..be6bc16e --- /dev/null +++ b/infrastructure/common.tfvars @@ -0,0 +1,14 @@ +project = "saet" +project_owner = "nhs-uec" +service = "uec-saet" +cost_centre = "P0675" +data_type = "PCD" +project_type = "Pilot" +public_facing = "no" +service_category = "bronze" +team_owner = "saet" + +artefacts_bucket_name = "artefacts-bucket" +s3_logging_bucket_name = "s3-access-logs" + +rds_port = 5432 diff --git a/infrastructure/environments/dev/environment.tfvars b/infrastructure/environments/dev/environment.tfvars new file mode 100644 index 00000000..9b5727d1 --- /dev/null +++ b/infrastructure/environments/dev/environment.tfvars @@ -0,0 +1,3 @@ +# environment specific values that are applicable to more than one stack +environment = "dev" +data_classification = "3" From 6c7355d333c4b48711bbe8b9a0698089e668689a Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:53:57 +0000 Subject: [PATCH 005/181] fix: Add github runner tfvars to infrastructure folder --- infrastructure/.gitignore | 1 + infrastructure/github_runner.tfvars | 1 + 2 files changed, 2 insertions(+) create mode 100644 infrastructure/github_runner.tfvars diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index 670f1db9..8c639d11 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -21,6 +21,7 @@ crash.*.log # Exception: Allow specific tfvars files to be committed !common.tfvars !environment.tfvars +!github_runner.tfvars # Ignore override files as they are usually used to override resources locally and so # are not checked in diff --git a/infrastructure/github_runner.tfvars b/infrastructure/github_runner.tfvars new file mode 100644 index 00000000..31a8fa56 --- /dev/null +++ b/infrastructure/github_runner.tfvars @@ -0,0 +1 @@ +github_org = "NHSDigital" From 24e4708874da1e9f5a263e2d5480f0b0d3c181d1 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:05:55 +0000 Subject: [PATCH 006/181] fix: Remove mgmt as it is not currently present --- .github/workflows/pipeline-deploy-policies.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index e383d5f0..52c05f38 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -11,7 +11,7 @@ on: - "task/**" paths: - "infrastructure/stacks/github_runner/**" - #- "infrastructure/stacks/account_policies/**" + - "infrastructure/stacks/account_policies/**" workflow_dispatch: # checkov:skip=CKV_GHA_7:Inputs reviewed and approved inputs: @@ -73,9 +73,6 @@ jobs: - name: "account" environment: ${{ needs.metadata.outputs.account_type }} stacks: "['github_runner']" - - name: "mgmt" - environment: ${{ needs.metadata.outputs.mgmt_environment }} - stacks: "['github_runner']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} From ff12b1e2a3c5816ed66dcd5bebf2eb596dcfa0fe Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:18:35 +0000 Subject: [PATCH 007/181] fix: Remove project_name variable and use project instead --- infrastructure/common/common-variables.tf | 5 ----- infrastructure/common/provider.tf | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/infrastructure/common/common-variables.tf b/infrastructure/common/common-variables.tf index d6ff628f..fc50d767 100644 --- a/infrastructure/common/common-variables.tf +++ b/infrastructure/common/common-variables.tf @@ -7,11 +7,6 @@ variable "project_owner" { description = "The owner of the project, based on organisation and department codes" } -variable "project_name" { - description = "Project name" - type = string -} - variable "aws_region" { type = string default = "eu-west-2" diff --git a/infrastructure/common/provider.tf b/infrastructure/common/provider.tf index cd0b0fde..4ab004e4 100644 --- a/infrastructure/common/provider.tf +++ b/infrastructure/common/provider.tf @@ -3,7 +3,7 @@ provider "aws" { default_tags { tags = { Environment = var.environment - Project = var.project_name + Project = var.project ManagedBy = "Terraform" } } From 4d4c149b2e429075016f9701a4f39a068df3275a Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 22 Jan 2026 19:49:08 +0000 Subject: [PATCH 008/181] fix: Use the correct role as the repo name changed the roles created as well --- .github/actions/configure-credentials/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/configure-credentials/action.yaml b/.github/actions/configure-credentials/action.yaml index f487f226..3dd4915f 100644 --- a/.github/actions/configure-credentials/action.yaml +++ b/.github/actions/configure-credentials/action.yaml @@ -23,6 +23,6 @@ runs: - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v5.1.1 with: - role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/saet-triage-api-dev-account-github-runner + role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ github.event.repository.name }}-${{ inputs.environment }}-${{ inputs.type }}-github-runner role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ inputs.aws_region }} From 069e5af95faa6887b4e558e33e6efaf02d05fd93 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:02:35 +0000 Subject: [PATCH 009/181] fix: Handle check english error --- scripts/githooks/check-english-usage.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/githooks/check-english-usage.sh b/scripts/githooks/check-english-usage.sh index 1c0c2064..baf6e6b7 100755 --- a/scripts/githooks/check-english-usage.sh +++ b/scripts/githooks/check-english-usage.sh @@ -62,10 +62,16 @@ function main() { # filter=[git command to filter the files to check] function run-vale-natively() { - # shellcheck disable=SC2046 + # Read files into an array to handle spaces in filenames + mapfile -t files < <($filter) + if [ ${#files[@]} -eq 0 ]; then + echo "No files to check" + return 0 + fi + vale \ --config "$PWD/scripts/config/vale/vale.ini" \ - $($filter) + "${files[@]}" return } @@ -82,17 +88,25 @@ function run-vale-in-docker() { echo run in "$DOCKER_CMD" # shellcheck disable=SC2155 local image=$(name=jdkato/vale docker-get-image-version-and-pull) + + # Read files into an array to handle spaces in filenames + mapfile -t files < <($filter) + # We use /dev/null here to stop `vale` from complaining that it's # not been called correctly if the $filter happens to return an # empty list. As long as there's a filename, even if it's one that # will be ignored, `vale` is happy. - # shellcheck disable=SC2046,SC2086 + if [ ${#files[@]} -eq 0 ]; then + files=(/dev/null) + fi + + # shellcheck disable=SC2086 $DOCKER_CMD run --rm --platform linux/amd64 \ --volume "$PWD:/workdir" \ --workdir /workdir \ "$image" \ --config /workdir/scripts/config/vale/vale.ini \ - $($filter) /dev/null + "${files[@]}" return } From 5bd03dcf38f7417de114916075e93196ae3513e9 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:05:16 +0000 Subject: [PATCH 010/181] fix: The formatting issue --- scripts/githooks/check-english-usage.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/githooks/check-english-usage.sh b/scripts/githooks/check-english-usage.sh index baf6e6b7..5297e084 100755 --- a/scripts/githooks/check-english-usage.sh +++ b/scripts/githooks/check-english-usage.sh @@ -68,7 +68,7 @@ function run-vale-natively() { echo "No files to check" return 0 fi - + vale \ --config "$PWD/scripts/config/vale/vale.ini" \ "${files[@]}" @@ -88,10 +88,10 @@ function run-vale-in-docker() { echo run in "$DOCKER_CMD" # shellcheck disable=SC2155 local image=$(name=jdkato/vale docker-get-image-version-and-pull) - + # Read files into an array to handle spaces in filenames mapfile -t files < <($filter) - + # We use /dev/null here to stop `vale` from complaining that it's # not been called correctly if the $filter happens to return an # empty list. As long as there's a filename, even if it's one that @@ -99,7 +99,7 @@ function run-vale-in-docker() { if [ ${#files[@]} -eq 0 ]; then files=(/dev/null) fi - + # shellcheck disable=SC2086 $DOCKER_CMD run --rm --platform linux/amd64 \ --volume "$PWD:/workdir" \ From 3b7de4def13a2f676861f150d24472f62c05f24a Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:10:43 +0000 Subject: [PATCH 011/181] fix: change the name --- .../stacks/github_runner/account_github_runner_policy.json.tpl | 2 +- .../stacks/github_runner/app_github_runner_policy.json.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl index dd9b87df..47df7b42 100644 --- a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl +++ b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl @@ -209,7 +209,7 @@ "Resource": "*" }, { - "Sid": "ShieldFullAccess", + "Sid": "AWSShieldFullAccess", "Effect": "Allow", "Action": [ "shield:*" diff --git a/infrastructure/stacks/github_runner/app_github_runner_policy.json.tpl b/infrastructure/stacks/github_runner/app_github_runner_policy.json.tpl index 79b251d2..59cbf8f4 100644 --- a/infrastructure/stacks/github_runner/app_github_runner_policy.json.tpl +++ b/infrastructure/stacks/github_runner/app_github_runner_policy.json.tpl @@ -226,7 +226,7 @@ "Resource": "*" }, { - "Sid": "ShieldFullAccess", + "Sid": "AWSShieldFullAccess", "Effect": "Allow", "Action": [ "shield:*" From c234a94350f9022d879ac7b0167b062b54f656e3 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:14:40 +0000 Subject: [PATCH 012/181] fix: Added for testing --- .github/workflows/pipeline-deploy-policies.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index 52c05f38..e049e0e0 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -85,7 +85,7 @@ jobs: manual-approval-permissions: name: "Manual approval for ${{ needs.metadata.outputs.environment }} permissions infrastructure deployment" - if: ${{ github.ref == 'refs/heads/main' && (needs.plan-permissions-infrastructure.outputs.plan_result == 'true') }} + if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up') && (needs.plan-permissions-infrastructure.outputs.plan_result == 'true') }} needs: - metadata - plan-permissions-infrastructure @@ -100,7 +100,7 @@ jobs: concurrency: group: "${{ matrix.environment }}-default-${{ matrix.name }}-permissions-${{matrix.stacks}}" cancel-in-progress: false - if: github.ref == 'refs/heads/main' + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up' }} needs: - metadata - manual-approval-permissions From d55c4bbe64f84a147a8f482fea12eb9d0bd57b0e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 23 Jan 2026 15:05:29 +0000 Subject: [PATCH 013/181] Adding the account polices stack and using the pipeline to deploy this --- .../workflows/pipeline-deploy-policies.yaml | 2 +- .../environments/prod/environment.tfvars | 0 .../environments/test/environment.tfvars | 11 ++ infrastructure/modules/lambda/data.tf | 99 ++++++++++ infrastructure/modules/lambda/locals.tf | 15 ++ infrastructure/modules/lambda/main.tf | 57 ++++++ infrastructure/modules/lambda/outputs.tf | 29 +++ infrastructure/modules/lambda/variables.tf | 143 ++++++++++++++ .../account_policies/api_gateway_account.tf | 7 + .../stacks/account_policies/iam_policies.tf | 130 +++++++++++++ .../stacks/account_policies/iam_roles.tf | 53 ++++++ .../account_policies/policies/ro_billing.json | 77 ++++++++ .../account_policies/policies/ro_compute.json | 33 ++++ .../policies/ro_data.json.tpl | 118 ++++++++++++ .../ro_infrastructure_management.json | 120 ++++++++++++ .../policies/ro_infrastructure_security.json | 70 +++++++ .../policies/ro_management.json | 144 ++++++++++++++ .../policies/ro_monitoring.json | 57 ++++++ .../policies/ro_networking.json | 40 ++++ .../policies/ro_security.json | 148 +++++++++++++++ .../account_policies/policies/rw_compute.json | 48 +++++ .../account_policies/policies/rw_data.json | 149 +++++++++++++++ .../rw_infrastructure_management.json | 150 +++++++++++++++ .../rw_infrastructure_resilience.json | 49 +++++ .../policies/rw_infrastructure_security.json | 126 ++++++++++++ .../policies/rw_management.json | 162 ++++++++++++++++ .../policies/rw_monitoring.json | 48 +++++ .../policies/rw_networking.json | 37 ++++ .../policies/rw_security.json | 179 ++++++++++++++++++ .../stacks/account_policies/variables.tf | 4 + 30 files changed, 2304 insertions(+), 1 deletion(-) create mode 100644 infrastructure/environments/prod/environment.tfvars create mode 100644 infrastructure/environments/test/environment.tfvars create mode 100644 infrastructure/modules/lambda/data.tf create mode 100644 infrastructure/modules/lambda/locals.tf create mode 100644 infrastructure/modules/lambda/main.tf create mode 100644 infrastructure/modules/lambda/outputs.tf create mode 100644 infrastructure/modules/lambda/variables.tf create mode 100644 infrastructure/stacks/account_policies/api_gateway_account.tf create mode 100644 infrastructure/stacks/account_policies/iam_policies.tf create mode 100644 infrastructure/stacks/account_policies/iam_roles.tf create mode 100644 infrastructure/stacks/account_policies/policies/ro_billing.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_compute.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_data.json.tpl create mode 100644 infrastructure/stacks/account_policies/policies/ro_infrastructure_management.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_infrastructure_security.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_management.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_monitoring.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_networking.json create mode 100644 infrastructure/stacks/account_policies/policies/ro_security.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_compute.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_data.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_infrastructure_management.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_infrastructure_resilience.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_infrastructure_security.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_management.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_monitoring.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_networking.json create mode 100644 infrastructure/stacks/account_policies/policies/rw_security.json create mode 100644 infrastructure/stacks/account_policies/variables.tf diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index e049e0e0..e6be5614 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -53,7 +53,7 @@ jobs: tag: ${{ inputs.tag }} environment: ${{ needs.metadata.outputs.environment }} workspace: "default" - stacks: "['github_runner']" + stacks: "['github_runner', 'account_policies']" type: account build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} secrets: inherit diff --git a/infrastructure/environments/prod/environment.tfvars b/infrastructure/environments/prod/environment.tfvars new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/environments/test/environment.tfvars b/infrastructure/environments/test/environment.tfvars new file mode 100644 index 00000000..28c6f84d --- /dev/null +++ b/infrastructure/environments/test/environment.tfvars @@ -0,0 +1,11 @@ +aws_region = "eu-west-2" +environment = "test" +project_name = "saet" + +#Lambda +mem_size = 512 +runtime = "python3.13" +s3_key = "lambda_function.zip" + +#Rest API +stage_name = "beta" diff --git a/infrastructure/modules/lambda/data.tf b/infrastructure/modules/lambda/data.tf new file mode 100644 index 00000000..4a8d12bc --- /dev/null +++ b/infrastructure/modules/lambda/data.tf @@ -0,0 +1,99 @@ +data "aws_iam_policy_document" "vpc_access_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "AllowVpcAccess" + effect = "Allow" + actions = [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:DescribeSubnets", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ] + resources = [ + "*" + ] + } +} + +data "aws_iam_policy_document" "deny_lambda_function_access_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "DenyLambdaFunctionAccess" + effect = "Deny" + actions = [ + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DetachNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ] + resources = ["*"] + condition { + test = "ArnEquals" + variable = "lambda:SourceFunctionArn" + values = [ + "arn:aws:lambda:${var.aws_region}:${var.account_id}:function:${var.function_name}${local.workspace_suffix}" + ] + } + } +} + +data "aws_iam_policy_document" "allow_private_subnet_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "AllowPrivateSubnetAccess" + effect = "Allow" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "ForAllValues:StringEquals" + variable = "lambda:SubnetIds" + values = var.subnet_ids + } + } +} + +data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + + statement { + sid = "EnforceStayInSpecificVpc" + effect = "Allow" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "lambda:VpcIds" + values = [var.vpc_id] + } + } +} + +data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "EnforceVpcFunction" + effect = "Deny" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "Null" + variable = "lambda:VpcIds" + values = ["true"] + } + } +} diff --git a/infrastructure/modules/lambda/locals.tf b/infrastructure/modules/lambda/locals.tf new file mode 100644 index 00000000..93069cfc --- /dev/null +++ b/infrastructure/modules/lambda/locals.tf @@ -0,0 +1,15 @@ + +# ============================================================================== +# Context + +locals { + workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" + environment_workspace = "${terraform.workspace}" == "default" ? "" : "${terraform.workspace}" + additional_json_policies = (concat(var.policy_jsons, [ + data.aws_iam_policy_document.allow_private_subnet_policy.json, + data.aws_iam_policy_document.limit_to_environment_vpc_policy.json, + data.aws_iam_policy_document.enforce_vpc_lambda_policy.json, + data.aws_iam_policy_document.deny_lambda_function_access_policy.json, + data.aws_iam_policy_document.vpc_access_policy.json + ])) +} diff --git a/infrastructure/modules/lambda/main.tf b/infrastructure/modules/lambda/main.tf new file mode 100644 index 00000000..e1422361 --- /dev/null +++ b/infrastructure/modules/lambda/main.tf @@ -0,0 +1,57 @@ +#trivy:ignore:AVD-AWS-0066 +module "lambda" { + # Module version: 8.1.0 + source = "git::https://github.com/terraform-aws-modules/terraform-aws-lambda.git?ref=1c3b16a8d9ee8944ba33f5327bdf011c6639cceb" + + function_name = "${var.function_name}${local.workspace_suffix}" + handler = var.handler + runtime = var.runtime + publish = var.publish + attach_policy_jsons = var.attach_policy_jsons + number_of_policy_jsons = length(local.additional_json_policies) + attach_tracing_policy = var.attach_tracing_policy + tracing_mode = var.tracing_mode + description = var.description + policy_jsons = local.additional_json_policies + timeout = var.timeout + memory_size = var.memory_size + + create_package = var.s3_bucket_name == "" ? var.create_package : false + local_existing_package = var.s3_bucket_name == "" ? var.local_existing_package : null + ignore_source_code_hash = var.ignore_source_code_hash + allowed_triggers = var.allowed_triggers + + s3_existing_package = var.s3_bucket_name != "" ? { + bucket = var.s3_bucket_name + key = var.s3_key + } : null + + vpc_subnet_ids = var.subnet_ids + vpc_security_group_ids = var.security_group_ids + + environment_variables = merge(var.environment_variables, { WORKSPACE = "${local.environment_workspace}" }) + layers = var.layers + + cloudwatch_logs_retention_in_days = var.cloudwatch_logs_retention + logging_system_log_level = var.cloudwatch_log_level +} + + + +resource "aws_lambda_function" "triage" { + function_name = var.function_name + role = var.lambda_role + handler = var.handler + runtime = var.runtime + s3_bucket = var.bucket_name + s3_key = var.s3_key + memory_size = var.mem_size + environment { + variables = merge( + var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, + var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, + var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, + var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} + ) + } +} diff --git a/infrastructure/modules/lambda/outputs.tf b/infrastructure/modules/lambda/outputs.tf new file mode 100644 index 00000000..4c272549 --- /dev/null +++ b/infrastructure/modules/lambda/outputs.tf @@ -0,0 +1,29 @@ + +output "lambda_name" { + value = module.lambda.lambda_function_name +} + +output "lambda_arn" { + value = module.lambda.lambda_function_arn +} + +output "lambda_function_arn" { + value = module.lambda.lambda_function_arn +} + +output "lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda.lambda_function_invoke_arn +} + +output "lambda_function_name" { + value = module.lambda.lambda_function_name +} + +output "lambda_role_arn" { + value = module.lambda.lambda_role_arn +} + +output "lambda_cloudwatch_log_group_name" { + value = module.lambda.lambda_cloudwatch_log_group_name +} diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf new file mode 100644 index 00000000..9f764b26 --- /dev/null +++ b/infrastructure/modules/lambda/variables.tf @@ -0,0 +1,143 @@ +# ============================================================================== +# Mandatory variables + +variable "function_name" { + description = "The function name of the Lambda" +} + +# variable "description" { +# description = "The description of the Lambda" +# } + +variable "policy_jsons" { + description = "List of JSON policies for Lambda" + default = [ + + ] +} + +# ============================================================================== +# Default variables +variable "handler" { + description = "Handler function entry point" + default = "app.lambda_handler" +} + +variable "runtime" { + description = "Runtime environment for the Lambda function" + default = "python3.12" +} + +# variable "publish" { +# description = "Whether to publish a new Lambda version on update" +# default = true +# } + +# variable "create_package" { +# description = "Whether to create a new ZIP package or use an existing one" +# default = false +# } + +# variable "local_existing_package" { +# description = "Path to the local ZIP file if using a pre-existing package" +# default = "./misc/init.zip" +# } + +# variable "ignore_source_code_hash" { +# description = "Whether to ignore changes to the source code hash" +# default = true +# } + +# variable "attach_tracing_policy" { +# default = false +# } + +# variable "tracing_mode" { +# description = "Tracing configuration for the Lambda function" +# type = string +# default = "PassThrough" +# } + +variable "attach_policy_jsons" { + description = "Whether to attach the provided JSON policies to the Lambda role" + default = true +} + +variable "number_of_policy_jsons" { + description = "Number of JSON policies to attach" + default = "1" +} + +variable "environment_variables" { + description = "Map of environment variables" + default = {} +} + +variable "layers" { + description = "The name of the Lambda layers" + default = [] +} + +variable "memory_size" { + description = "Amount of memory in MB your Lambda Function can use at runtime" + default = "512" +} + +variable "timeout" { + description = "Timeout of the lambda function in seconds" + default = "3" +} + +# variable "subnet_ids" { +# description = "List of subnet IDs for the Lambda function VPC configuration" +# } + +# variable "security_group_ids" { +# description = "List of security group IDs for the Lambda function VPC configuration" +# } + +variable "s3_bucket_name" { + description = "Name of the S3 bucket where the Lambda package is stored" +} + +variable "s3_key" { + description = "S3 key (path) to the Lambda package inside the S3 bucket" +} + +variable "allowed_triggers" { + description = "List of allowed triggers for the Lambda function" + type = map(any) + default = {} +} + +# variable "account_prefix" { +# description = "Prefix for the account resources, typically includes the repo name and environment" +# type = string +# } + +# variable "account_id" { +# description = "AWS account ID" +# type = string +# } + +variable "aws_region" { + description = "AWS region where the Lambda function will be deployed" + type = string +} + +# variable "vpc_id" { +# description = "Id of the VPC into which the Lambda function will be deployed" +# type = string +# } + +# variable "cloudwatch_logs_retention" { +# description = "Number of days to retain CloudWatch logs" +# type = number +# default = 30 +# } + +# variable "cloudwatch_log_level" { +# description = "Logging level for CloudWatch logs" +# type = string +# default = "INFO" +# } diff --git a/infrastructure/stacks/account_policies/api_gateway_account.tf b/infrastructure/stacks/account_policies/api_gateway_account.tf new file mode 100644 index 00000000..d8d59103 --- /dev/null +++ b/infrastructure/stacks/account_policies/api_gateway_account.tf @@ -0,0 +1,7 @@ +resource "aws_api_gateway_account" "api_gateway_account" { + cloudwatch_role_arn = aws_iam_role.cloudwatch_api_gateway_role.arn + + depends_on = [ + aws_iam_role_policy_attachment.api_gateway_cloudwatch_policy_attachment + ] +} diff --git a/infrastructure/stacks/account_policies/iam_policies.tf b/infrastructure/stacks/account_policies/iam_policies.tf new file mode 100644 index 00000000..18508b1f --- /dev/null +++ b/infrastructure/stacks/account_policies/iam_policies.tf @@ -0,0 +1,130 @@ +locals { + billing_ro_policy = jsondecode(file("${path.module}/policies/ro_billing.json")) + compute_rw_policy = jsondecode(file("${path.module}/policies/rw_compute.json")) + compute_ro_policy = jsondecode(file("${path.module}/policies/ro_compute.json")) + data_rw_policy = jsondecode(file("${path.module}/policies/rw_data.json")) + networking_rw_policy = jsondecode(file("${path.module}/policies/rw_networking.json")) + networking_ro_policy = jsondecode(file("${path.module}/policies/ro_networking.json")) + security_rw_policy = jsondecode(file("${path.module}/policies/rw_security.json")) + security_ro_policy = jsondecode(file("${path.module}/policies/ro_security.json")) + monitoring_rw_policy = jsondecode(file("${path.module}/policies/rw_monitoring.json")) + monitoring_ro_policy = jsondecode(file("${path.module}/policies/ro_monitoring.json")) + management_rw_policy = jsondecode(file("${path.module}/policies/rw_management.json")) + management_ro_policy = jsondecode(file("${path.module}/policies/ro_management.json")) + infrastructure_security_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_security.json")) + infrastructure_security_ro_policy = jsondecode(file("${path.module}/policies/ro_infrastructure_security.json")) + infrastructure_management_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_management.json")) + infrastructure_management_ro_policy = jsondecode(file("${path.module}/policies/ro_infrastructure_management.json")) + infrastructure_resilience_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_resilience.json")) + data_ro_policy = jsondecode(templatefile("${path.module}/policies/ro_data.json.tpl", { + athena_output_bucket_name = var.athena_output_bucket_name + })) +} + +resource "aws_iam_policy" "billing_policy_ro" { + name = "ro_billing" + description = "Read-only policies for aws billing services" + policy = jsonencode(local.billing_ro_policy) +} + +resource "aws_iam_policy" "compute_policy_rw" { + name = "rw_compute" + description = "Read-write policies for aws compute-related services" + policy = jsonencode(local.compute_rw_policy) +} + +resource "aws_iam_policy" "compute_policy_ro" { + name = "ro_compute" + description = "Read-only policies for aws compute-related services" + policy = jsonencode(local.compute_ro_policy) +} + +resource "aws_iam_policy" "data_rw" { + name = "rw_data" + description = "Read-write policies for aws data services" + policy = jsonencode(local.data_rw_policy) +} + +resource "aws_iam_policy" "data_ro" { + name = "ro_data" + description = "Read-only policies for aws data services" + policy = jsonencode(local.data_ro_policy) +} + +resource "aws_iam_policy" "networking_rw" { + name = "rw_networking" + description = "Read-write policies for aws networking services" + policy = jsonencode(local.networking_rw_policy) +} + +resource "aws_iam_policy" "networking_ro" { + name = "ro_networking" + description = "Read-only policies for aws networking services" + policy = jsonencode(local.networking_ro_policy) +} + +resource "aws_iam_policy" "security_rw" { + name = "rw_security" + description = "Read-write policies for aws security services" + policy = jsonencode(local.security_rw_policy) +} + +resource "aws_iam_policy" "security_ro" { + name = "ro_security" + description = "Read-only policies for aws security services" + policy = jsonencode(local.security_ro_policy) +} + +resource "aws_iam_policy" "monitoring_rw" { + name = "rw_monitoring" + description = "Read-write policies for aws monitoring services" + policy = jsonencode(local.monitoring_rw_policy) +} + +resource "aws_iam_policy" "monitoring_ro" { + name = "ro_monitoring" + description = "Read-only policies for aws monitoring services" + policy = jsonencode(local.monitoring_ro_policy) +} + +resource "aws_iam_policy" "management_rw" { + name = "rw_management" + description = "Read-write policies for aws management services" + policy = jsonencode(local.management_rw_policy) +} + +resource "aws_iam_policy" "management_ro" { + name = "ro_management" + description = "Read-only policies for aws management services" + policy = jsonencode(local.management_ro_policy) +} + +resource "aws_iam_policy" "infrastructure_security_rw" { + name = "rw_infrastructure_security" + description = "Read-write policies for aws infrastructure security services" + policy = jsonencode(local.infrastructure_security_rw_policy) +} + +resource "aws_iam_policy" "infrastructure_security_ro" { + name = "ro_infrastructure_security" + description = "Read-only policies for aws infrastructure security services" + policy = jsonencode(local.infrastructure_security_ro_policy) +} + +resource "aws_iam_policy" "infrastructure_management_rw" { + name = "rw_infrastructure_management" + description = "Read-write policies for aws infrastructure management services" + policy = jsonencode(local.infrastructure_management_rw_policy) +} + +resource "aws_iam_policy" "infrastructure_management_ro" { + name = "ro_infrastructure_management" + description = "Read-only policies for aws infrastructure management services" + policy = jsonencode(local.infrastructure_management_ro_policy) +} + +resource "aws_iam_policy" "infrastructure_resilience_rw" { + name = "rw_infrastructure_resilience" + description = "Read-write policies for aws resilience hub services" + policy = jsonencode(local.infrastructure_resilience_rw_policy) +} diff --git a/infrastructure/stacks/account_policies/iam_roles.tf b/infrastructure/stacks/account_policies/iam_roles.tf new file mode 100644 index 00000000..5f1e5c0d --- /dev/null +++ b/infrastructure/stacks/account_policies/iam_roles.tf @@ -0,0 +1,53 @@ +# resource "aws_iam_role" "dms_vpc_role" { +# name = "dms-vpc-role" +# description = "Allows DMS to manage VPC" +# assume_role_policy = jsonencode({ +# Version = "2012-10-17" +# Statement = [ +# { +# Effect = "Allow" +# Principal = { +# Service = "dms.amazonaws.com" +# } +# Action = "sts:AssumeRole" +# }, +# ] +# }) +# } + +# resource "aws_iam_role_policy_attachment" "dms_vpc_role_policy_attachment" { +# role = aws_iam_role.dms_vpc_role.name +# policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole" +# } + +# Create the service-linked role for Shield Advanced +# This ensures the role exists before we try to use Shield Advanced features +resource "aws_iam_service_linked_role" "shield" { + aws_service_name = "shield.amazonaws.com" + description = "Service-linked role for AWS Shield Advanced" +} + +# Role and policy for allowing the REST variant of the API Gateway to write logs to specifically named log groups +# in the account +resource "aws_iam_role" "cloudwatch_api_gateway_role" { + name = "${var.project}-api-gateway-cloudwatch" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["apigateway.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role_policy_attachment" "api_gateway_cloudwatch_policy_attachment" { + role = aws_iam_role.cloudwatch_api_gateway_role.id + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" +} diff --git a/infrastructure/stacks/account_policies/policies/ro_billing.json b/infrastructure/stacks/account_policies/policies/ro_billing.json new file mode 100644 index 00000000..5b762840 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_billing.json @@ -0,0 +1,77 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "BillingReadOnly", + "Effect": "Allow", + "Action": [ + "account:GetAccountInformation", + "aws-portal:ViewBilling", + "billing:GetBillingData", + "billing:GetBillingDetails", + "billing:GetBillingNotifications", + "billing:GetBillingPreferences", + "billing:GetCredits", + "billing:GetContractInformation", + "billing:GetIAMAccessPreference", + "billing:GetSellerOfRecord", + "billing:ListBillingViews", + "budgets:ViewBudget", + "budgets:DescribeBudgetActionsForBudget", + "budgets:DescribeBudgetAction", + "budgets:DescribeBudgetActionsForAccount", + "budgets:DescribeBudgetActionHistories", + "ce:DescribeCostCategoryDefinition", + "ce:GetCostAndUsage", + "ce:ListCostCategoryDefinitions", + "ce:ListTagsForResource", + "ce:ListCostAllocationTags", + "ce:ListCostAllocationTagBackfillHistory", + "ce:GetTags", + "ce:GetDimensionValues", + "consolidatedbilling:ListLinkedAccounts", + "consolidatedbilling:GetAccountBillingRole", + "cur:GetClassicReport", + "cur:GetClassicReportPreferences", + "cur:GetUsageReport", + "cur:DescribeReportDefinitions", + "freetier:GetFreeTierAlertPreference", + "freetier:GetFreeTierUsage", + "invoicing:BatchGetInvoiceProfile", + "invoicing:GetInvoiceEmailDeliveryPreferences", + "invoicing:GetInvoicePDF", + "invoicing:GetInvoiceUnit", + "invoicing:ListInvoiceSummaries", + "invoicing:ListInvoiceUnits", + "invoicing:ListTagsForResource", + "mapcredits:ListQuarterSpend", + "mapcredits:ListAssociatedPrograms", + "mapcredits:ListQuarterCredits", + "payments:GetFinancingApplication", + "payments:GetFinancingLine", + "payments:GetFinancingLineWithdrawal", + "payments:GetFinancingOption", + "payments:GetPaymentInstrument", + "payments:GetPaymentStatus", + "payments:ListFinancingApplications", + "payments:ListFinancingLines", + "payments:ListFinancingLineWithdrawals", + "payments:ListPaymentInstruments", + "payments:ListPaymentPreferences", + "payments:ListPaymentProgramOptions", + "payments:ListPaymentProgramStatus", + "payments:ListTagsForResource", + "purchase-orders:GetPurchaseOrder", + "purchase-orders:ViewPurchaseOrders", + "purchase-orders:ListPurchaseOrderInvoices", + "purchase-orders:ListPurchaseOrders", + "purchase-orders:ListTagsForResource", + "sustainability:GetCarbonFootprintSummary", + "tax:GetTaxRegistrationDocument", + "tax:GetTaxInheritance", + "tax:ListTaxRegistrations" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_compute.json b/infrastructure/stacks/account_policies/policies/ro_compute.json new file mode 100644 index 00000000..8835147a --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_compute.json @@ -0,0 +1,33 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ComputeReadOnly", + "Effect": "Allow", + "Action": [ + "ec2:Describe*", + "ec2:GetSecurityGroupsForVpc", + "ec2:GetVerifiedAccessInstanceWebAcl", + "elasticloadbalancing:Describe*", + "autoscaling:Describe*", + "lambda:Get*", + "lambda:List*", + "states:DescribeStateMachine", + "states:ListStateMachines", + "tag:GetResources" + ], + "Resource": "*" + }, + { + "Sid": "RunAthenaLambdas", + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": [ + "arn:aws:lambda:*:*:function:athenafederatedcatalog_athena_federated_twr", + "arn:aws:lambda:*:*:function:athenafederatedcatalog_athena_federated_rds_twr" + ] + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl new file mode 100644 index 00000000..936952fe --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl @@ -0,0 +1,118 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DataPipelineReadOnlyAccess", + "Action": [ + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:QueryObjects" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "KinesisReadOnlyAccess", + "Action": [ + "kinesis:ListStreams", + "kinesis:DescribeStream", + "kinesis:DescribeStreamSummary" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "ResourceGroupsReadOnlyAccess", + "Effect": "Allow", + "Action": [ + "resource-groups:ListGroups", + "resource-groups:ListGroupResources", + "resource-groups:GetGroup", + "resource-groups:GetGroupQuery" + ], + "Resource": "*" + }, + { + "Sid": "DynamoReadOnly", + "Effect": "Allow", + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:Describe*", + "dynamodb:List*", + "dynamodb:GetAbacStatus", + "dynamodb:GetItem", + "dynamodb:GetResourcePolicy", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:PartiQLSelect", + "dax:Describe*", + "dax:List*", + "dax:GetItem", + "dax:BatchGetItem", + "dax:Query", + "dax:Scan" + ], + "Resource": "*" + }, + { + "Sid": "RDSReadOnly", + "Effect": "Allow", + "Action": [ + "rds:Describe*", + "rds:ListTagsForResource" + ], + "Resource": "*" + }, + { + "Sid": "S3ReadOnly", + "Effect": "Allow", + "Action": [ + "s3:Get*", + "s3:List*", + "s3:Describe*", + "s3-object-lambda:Get*", + "s3-object-lambda:List*" + ], + "Resource": "*" + }, + { + "Sid": "S3ForAthena", + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": "arn:aws:s3:::${athena_output_bucket_name}/*" + }, + { + "Sid": "AthenaLimitedAccess", + "Effect": "Allow", + "Action": [ + "athena:Get*", + "athena:List*", + "athena:StartQueryExecution", + "athena:UpdateNamedQuery", + "athena:StopQueryExecution", + "athena:CreatePreparedStatement", + "athena:UpdatePreparedStatement", + "athena:CreateNamedQuery", + "athena:CancelQueryExecution", + "athena:BatchGetNamedQuery" + ], + "Resource": "*" + }, + { + "Sid": "DMSReadOnlyAccess", + "Action": [ + "dms:Describe*", + "dms:List*", + "dms:TestConnection", + "dms:DescribeReplicationTasks", + "dms:DescribeEndpoints" + ], + "Effect": "Allow", + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_infrastructure_management.json b/infrastructure/stacks/account_policies/policies/ro_infrastructure_management.json new file mode 100644 index 00000000..b46331e3 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_infrastructure_management.json @@ -0,0 +1,120 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "WAReadOnly", + "Effect": "Allow", + "Action": [ + "wellarchitected:Get*", + "wellarchitected:List*", + "wellarchitected:ExportLens" + ], + "Resource": "*" + }, + { + "Sid": "CloudformationReadOnly", + "Effect": "Allow", + "Action": [ + "cloudformation:Describe*", + "cloudformation:EstimateTemplateCost", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:ValidateTemplate", + "cloudformation:Detect*" + ], + "Resource": "*" + }, + { + "Sid": "TrustedAdvisorReadOnly", + "Effect": "Allow", + "Action": [ + "trustedadvisor:DescribeAccount*", + "trustedadvisor:DescribeOrganization", + "trustedadvisor:DescribeRisk*", + "trustedadvisor:DownloadRisk", + "trustedadvisor:DescribeNotificationConfigurations" + ], + "Resource": "*" + }, + { + "Sid": "OrganizationsTrustAdvisorReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:ListDelegatedAdministrators" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "organizations:ServicePrincipal": [ + "reporting.trustedadvisor.amazonaws.com" + ] + } + } + }, + { + "Sid": "OrganizationsHealthReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:EnableAWSServiceAccess", + "organizations:DisableAWSServiceAccess" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "organizations:ServicePrincipal": "health.amazonaws.com" + } + } + }, + { + "Sid": "AWSHealthReadOnly", + "Effect": "Allow", + "Action": [ + "health:*" + ], + "Resource": "*" + }, + { + "Sid": "OrganizationsReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:ListAccounts", + "organizations:ListParents", + "organizations:DescribeAccount", + "organizations:ListDelegatedAdministrators", + "organizations:DescribeOrganization", + "organizations:ListAWSServiceAccessForOrganization", + "organizations:DescribeOrganizationalUnit" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "health.amazonaws.com" + } + } + }, + { + "Sid": "SupportReadOnlyAccess", + "Effect": "Allow", + "Action": [ + "support:DescribeAttachment", + "support:DescribeCases", + "support:DescribeCommunications", + "support:DescribeServices", + "support:DescribeSeverityLevels", + "support:DescribeSupportLevel", + "support:DescribeTrustedAdvisorCheck", + "support:DescribeTrustedAdvisorCheckRefreshStatuses", + "support:DescribeTrustedAdvisorCheckResult", + "support:DescribeTrustedAdvisorCheckSummaries", + "support:DescribeTrustedAdvisorChecks", + "support:SearchForCases" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_infrastructure_security.json b/infrastructure/stacks/account_policies/policies/ro_infrastructure_security.json new file mode 100644 index 00000000..a6a4de5c --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_infrastructure_security.json @@ -0,0 +1,70 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudTrailReadOnly", + "Effect": "Allow", + "Action": [ + "cloudtrail:Describe*", + "cloudtrail:Get*", + "cloudtrail:List*", + "cloudtrail:LookupEvents" + ], + "Resource": "*" + }, + { + "Sid": "SecurityHubReadOnly", + "Effect": "Allow", + "Action": [ + "securityhub:BatchGet*", + "securityhub:Describe*", + "securityhub:Get*", + "securityhub:List*" + ], + "Resource": "*" + }, + { + "Sid": "Inspector2AndCodeGuruSecurityReadOnly", + "Effect": "Allow", + "Action": [ + "inspector2:BatchGet*", + "inspector2:Describe*", + "inspector2:Get*", + "inspector2:List*", + "inspector2:Search*", + "codeguru-security:BatchGetFindings", + "codeguru-security:GetAccountConfiguration" + ], + "Resource": "*" + }, + { + "Sid": "GuardDutyReadOnly", + "Effect": "Allow", + "Action": [ + "guardduty:Describe*", + "guardduty:Get*", + "guardduty:List*" + ], + "Resource": "*" + }, + { + "Sid": "FirewallManagerReadOnly", + "Effect": "Allow", + "Action": [ + "fms:Get*", + "fms:List*" + ], + "Resource": "*" + }, + { + "Sid": "KMSKeyReadOnly", + "Effect": "Allow", + "Action": [ + "kms:ListAliases", + "kms:ListKeys", + "kms:DescribeKey" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_management.json b/infrastructure/stacks/account_policies/policies/ro_management.json new file mode 100644 index 00000000..2e23ca29 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_management.json @@ -0,0 +1,144 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ApiGatewayReadOnly", + "Effect": "Allow", + "Action": "apigateway:GET", + "Resource": "arn:aws:apigateway:*::/*" + }, + { + "Sid": "EventBridgeAndSchedulerReadOnly", + "Effect": "Allow", + "Action": [ + "events:DescribeArchive", + "events:DescribeConnection", + "events:DescribeEndpoint", + "events:DescribeEventBus", + "events:DescribeEventSource", + "events:DescribeReplay", + "events:DescribeRule", + "events:ListApiDestinations", + "events:ListArchives", + "events:ListConnections", + "events:ListEndpoints", + "events:ListEventBuses", + "events:ListEventSources", + "events:ListReplays", + "events:ListRuleNamesByTarget", + "events:ListRules", + "events:ListTargetsByRule", + "events:TestEventPattern", + "pipes:DescribePipe", + "pipes:ListPipes", + "pipes:ListTagsForResource", + "scheduler:GetSchedule", + "scheduler:GetScheduleGroup", + "scheduler:ListScheduleGroups", + "scheduler:ListSchedules", + "scheduler:ListTagsForResource", + "schemas:DescribeCodeBinding", + "schemas:DescribeDiscoverer", + "schemas:DescribeRegistry", + "schemas:DescribeSchema", + "schemas:ExportSchema", + "schemas:GetCodeBindingSource", + "schemas:GetDiscoveredSchema", + "schemas:GetResourcePolicy", + "schemas:ListDiscoverers", + "schemas:ListRegistries", + "schemas:ListSchemaVersions", + "schemas:ListSchemas", + "schemas:ListTagsForResource", + "schemas:SearchSchemas" + ], + "Resource": "*" + }, + { + "Sid": "SSMReadOnly", + "Effect": "Allow", + "Action": [ + "ssm:Describe*", + "ssm:Get*", + "ssm:List*" + ], + "Resource": "*" + }, + { + "Sid": "OpenSearchServiceReadOnly", + "Effect": "Allow", + "Action": [ + "es:Describe*", + "es:Get*", + "es:List*", + "osis:Get*", + "osis:List*", + "opensearch:Describe*", + "opensearch:Get*", + "opensearch:List*", + "aoss:Describe*", + "aoss:Get*", + "aoss:List*" + ], + "Resource": "*" + }, + { + "Sid": "SQSReadOnly", + "Effect": "Allow", + "Action": [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ListDeadLetterSourceQueues", + "sqs:ListMessageMoveTasks", + "sqs:ListQueueTags", + "sqs:ListQueues" + ], + "Resource": "*" + }, + { + "Sid": "SNSReadOnly", + "Effect": "Allow", + "Action": [ + "sns:CheckIfPhoneNumberIsOptedOut", + "sns:Get*", + "sns:List*" + ], + "Resource": "*" + }, + { + "Sid": "SecretsManagerReadOnly", + "Effect": "Allow", + "Action": [ + "secretsmanager:BatchGetSecretValue", + "secretsmanager:DescribeSecret", + "secretsmanager:GetRandomPassword", + "secretsmanager:GetResourcePolicy", + "secretsmanager:GetSecretValue", + "secretsmanager:ListSecretVersionIds", + "secretsmanager:ListSecrets" + ], + "Resource": "*" + }, + { + "Sid": "AppConfigReadOnly", + "Effect": "Allow", + "Action": [ + "appconfig:GetApplication", + "appconfig:GetEnvironment", + "appconfig:GetConfiguration", + "appconfig:GetConfigurationProfile", + "appconfig:GetDeployment", + "appconfig:GetDeploymentStrategy", + "appconfig:GetHostedConfigurationVersion", + "appconfig:ListApplications", + "appconfig:ListConfigurationProfiles", + "appconfig:ListDeployments", + "appconfig:ListDeploymentStrategies", + "appconfig:ListEnvironments", + "appconfig:ListHostedConfigurationVersions", + "appconfig:ListTagsForResource" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_monitoring.json b/infrastructure/stacks/account_policies/policies/ro_monitoring.json new file mode 100644 index 00000000..dbcace04 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_monitoring.json @@ -0,0 +1,57 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudWatchAndMonitoringReadOnly", + "Effect": "Allow", + "Action": [ + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingActivities", + "application-autoscaling:DescribeScalingPolicies", + "application-signals:BatchGet*", + "application-signals:Get*", + "application-signals:List*", + "cloudwatch:BatchGet*", + "cloudwatch:Describe*", + "cloudwatch:GenerateQuery", + "cloudwatch:Get*", + "cloudwatch:List*", + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricStatistics", + "cloudwatch:GetMetricData", + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:GetInsightRuleReport", + "logs:Describe*", + "logs:FilterLogEvents", + "logs:Get*", + "logs:List*", + "logs:StartLiveTail", + "logs:StartQuery", + "logs:StopLiveTail", + "logs:StopQuery", + "logs:TestMetricFilter", + "logs:DescribeLogStreams", + "logs:GetLogEvents", + "logs:DescribeQueries", + "logs:GetLogGroupFields", + "logs:GetLogRecord", + "logs:GetQueryResults", + "oam:ListSinks", + "rum:BatchGet*", + "rum:Get*", + "rum:List*", + "synthetics:Describe*", + "synthetics:Get*", + "synthetics:List*", + "xray:BatchGet*", + "xray:CancelTraceRetrieval", + "xray:Get*", + "xray:List*", + "xray:StartTraceRetrieval" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_networking.json b/infrastructure/stacks/account_policies/policies/ro_networking.json new file mode 100644 index 00000000..342233db --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_networking.json @@ -0,0 +1,40 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ACMCertificateReadOnly", + "Effect": "Allow", + "Action": [ + "acm:DescribeCertificate", + "acm:GetAccountConfiguration", + "acm:GetCertificate", + "acm:ListCertificates", + "acm:ListTagsForCertificate" + ], + "Resource": "*" + }, + { + "Sid": "CloudFrontReadOnly", + "Effect": "Allow", + "Action": [ + "cloudfront:Describe*", + "cloudfront:Get*", + "cloudfront:List*", + "cloudfront-keyvaluestore:Describe*", + "cloudfront-keyvaluestore:Get*", + "cloudfront-keyvaluestore:List*" + ], + "Resource": "*" + }, + { + "Sid": "Route53ReadOnly", + "Effect": "Allow", + "Action": [ + "route53:Get*", + "route53:List*", + "route53:TestDNSAnswer" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/ro_security.json b/infrastructure/stacks/account_policies/policies/ro_security.json new file mode 100644 index 00000000..2a412374 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/ro_security.json @@ -0,0 +1,148 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "IAMReadOnly", + "Effect": "Allow", + "Action": [ + "iam:Get*", + "iam:List*", + "iam:SimulateCustomPolicy", + "iam:SimulatePrincipalPolicy" + ], + "Resource": "*" + }, + { + "Sid": "WAFClassicReadOnly", + "Effect": "Allow", + "Action": [ + "waf:Get*", + "waf:List*", + "waf-regional:Get*", + "waf-regional:List*" + ], + "Resource": [ + "arn:aws:waf::*:bytematchset/*", + "arn:aws:waf::*:ipset/*", + "arn:aws:waf::*:ratebasedrule/*", + "arn:aws:waf::*:rule/*", + "arn:aws:waf::*:sizeconstraintset/*", + "arn:aws:waf::*:sqlinjectionset/*", + "arn:aws:waf::*:webacl/*", + "arn:aws:waf::*:xssmatchset/*", + "arn:aws:waf::*:regexmatch/*", + "arn:aws:waf::*:regexpatternset/*", + "arn:aws:waf::*:geomatchset/*", + "arn:aws:waf::*:rulegroup/*", + "arn:aws:waf:*:*:changetoken/*", + "arn:aws:waf-regional:*:*:bytematchset/*", + "arn:aws:waf-regional:*:*:ipset/*", + "arn:aws:waf-regional:*:*:ratebasedrule/*", + "arn:aws:waf-regional:*:*:rule/*", + "arn:aws:waf-regional:*:*:sizeconstraintset/*", + "arn:aws:waf-regional:*:*:sqlinjectionset/*", + "arn:aws:waf-regional:*:*:webacl/*", + "arn:aws:waf-regional:*:*:xssmatchset/*", + "arn:aws:waf-regional:*:*:regexmatch/*", + "arn:aws:waf-regional:*:*:regexpatternset/*", + "arn:aws:waf-regional:*:*:geomatchset/*", + "arn:aws:waf-regional:*:*:rulegroup/*", + "arn:aws:waf-regional:*:*:changetoken/*" + ] + }, + { + "Sid": "AllowWAFClassicGetWebACLForResource", + "Effect": "Allow", + "Action": [ + "waf-regional:GetWebACLForResource" + ], + "Resource": "arn:aws:waf-regional:*:*:*/*" + }, + { + "Sid": "AWSWAFV2ReadOnly", + "Effect": "Allow", + "Action": [ + "wafv2:Get*", + "wafv2:List*", + "wafv2:Describe*", + "wafv2:CheckCapacity" + ], + "Resource": [ + "arn:aws:wafv2:*:*:*/webacl/*/*", + "arn:aws:wafv2:*:*:*/ipset/*/*", + "arn:aws:wafv2:*:*:*/managedruleset/*/*", + "arn:aws:wafv2:*:*:*/rulegroup/*/*", + "arn:aws:wafv2:*:*:*/regexpatternset/*/*" + ] + }, + { + "Sid": "AllowListActionsForAppSync", + "Effect": "Allow", + "Action": [ + "appsync:ListGraphqlApis" + ], + "Resource": "*" + }, + { + "Sid": "AllowGetActionForCognito", + "Effect": "Allow", + "Action": [ + "cognito-idp:GetWebACLForResource" + ], + "Resource": "arn:aws:cognito-idp:*:*:userpool/*" + }, + { + "Sid": "AllowListActionsForCognito", + "Effect": "Allow", + "Action": [ + "cognito-idp:ListUserPools", + "cognito-idp:ListResourcesForWebACL" + ], + "Resource": "*" + }, + { + "Sid": "AllowGetActionForAppRunner", + "Effect": "Allow", + "Action": [ + "apprunner:DescribeWebAclForService" + ], + "Resource": "arn:aws:apprunner:*:*:service/*/*" + }, + { + "Sid": "AllowListActionsForAppRunner", + "Effect": "Allow", + "Action": [ + "apprunner:ListServices", + "apprunner:ListAssociatedServicesForWebAcl" + ], + "Resource": "*" + }, + { + "Sid": "AllowGetActionForAmplify", + "Effect": "Allow", + "Action": [ + "amplify:GetWebACLForResource" + ], + "Resource": "arn:aws:amplify:*:*:apps/*" + }, + { + "Sid": "AllowListActionsForAmplify", + "Effect": "Allow", + "Action": [ + "amplify:ListApps", + "amplify:ListResourcesForWebACL" + ], + "Resource": "*" + }, + { + "Sid": "ShieldReadOnly", + "Effect": "Allow", + "Action": [ + "shield:Describe*", + "shield:Get*", + "shield:List*" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_compute.json b/infrastructure/stacks/account_policies/policies/rw_compute.json new file mode 100644 index 00000000..b2345a89 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_compute.json @@ -0,0 +1,48 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ComputeFullAccess", + "Action": [ + "ec2:*", + "elasticloadbalancing:*", + "autoscaling:*", + "kms:ListAliases", + "lambda:*", + "states:DescribeStateMachine", + "states:ListStateMachines", + "tag:GetResources", + "tiros:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": [ + "autoscaling.amazonaws.com", + "ec2scheduled.amazonaws.com", + "elasticloadbalancing.amazonaws.com", + "spot.amazonaws.com", + "spotfleet.amazonaws.com", + "transitgateway.amazonaws.com" + ] + } + } + }, + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:PassedToService": "lambda.amazonaws.com" + } + } + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_data.json b/infrastructure/stacks/account_policies/policies/rw_data.json new file mode 100644 index 00000000..55894853 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_data.json @@ -0,0 +1,149 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "DataPipelineFullAccess", + "Action": [ + "datapipeline:ActivatePipeline", + "datapipeline:CreatePipeline", + "datapipeline:DeletePipeline", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:PutPipelineDefinition", + "datapipeline:QueryObjects" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "KinesisFullAccess", + "Action": [ + "kinesis:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Sid": "ResourceGroupsFullAccess", + "Effect": "Allow", + "Action": [ + "resource-groups:ListGroups", + "resource-groups:ListGroupResources", + "resource-groups:GetGroup", + "resource-groups:GetGroupQuery", + "resource-groups:DeleteGroup", + "resource-groups:CreateGroup" + ], + "Resource": "*" + }, + { + "Sid": "DynamoFullAccess", + "Effect": "Allow", + "Action": [ + "dynamodb:*", + "dax:*" + ], + "Resource": "*" + }, + { + "Sid": "PassRoleForScalingAndDAX", + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": "*", + "Condition": { + "StringLike": { + "iam:PassedToService": [ + "application-autoscaling.amazonaws.com", + "application-autoscaling.amazonaws.com.cn", + "dax.amazonaws.com" + ] + } + } + }, + { + "Sid": "CreateServiceLinkedRoleForDynamoAndDAX", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": [ + "replication.dynamodb.amazonaws.com", + "dax.amazonaws.com", + "dynamodb.application-autoscaling.amazonaws.com", + "contributorinsights.dynamodb.amazonaws.com", + "kinesisreplication.dynamodb.amazonaws.com" + ] + } + } + }, + { + "Sid": "RDSFullAccess", + "Effect": "Allow", + "Action": [ + "rds:*" + ], + "Resource": "*" + }, + { + "Sid": "DMSFullAccess", + "Effect": "Allow", + "Action": [ + "dms:*" + ], + "Resource": "*" + }, + { + "Sid": "PerformanceInsightsFullAccess", + "Effect": "Allow", + "Action": "pi:*", + "Resource": [ + "arn:aws:pi:*:*:metrics/rds/*", + "arn:aws:pi:*:*:perf-reports/rds/*" + ] + }, + { + "Sid": "CreateServiceLinkedRoleForRDS", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringLike": { + "iam:AWSServiceName": [ + "rds.amazonaws.com", + "rds.application-autoscaling.amazonaws.com" + ] + } + } + }, + { + "Sid": "S3FullAccess", + "Effect": "Allow", + "Action": [ + "s3:*", + "s3-object-lambda:*" + ], + "Resource": "*" + }, + { + "Sid": "GlueFullAccess", + "Effect": "Allow", + "Action": "glue:*", + "Resource": "*" + }, + { + "Sid": "AthenaFullAccess", + "Effect": "Allow", + "Action": "athena:*", + "Resource": "*" + }, + { + "Sid": "QuicksiteFullAccess", + "Effect": "Allow", + "Action": "quicksight:*", + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_infrastructure_management.json b/infrastructure/stacks/account_policies/policies/rw_infrastructure_management.json new file mode 100644 index 00000000..08a936fd --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_infrastructure_management.json @@ -0,0 +1,150 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "WAReadFullAccess", + "Effect": "Allow", + "Action": [ + "wellarchitected:*" + ], + "Resource": "*" + }, + { + "Sid": "CloudformationFullAccess", + "Effect": "Allow", + "Action": [ + "cloudformation:*" + ], + "Resource": "*" + }, + { + "Sid": "TrustedAdvisorFullAccess", + "Effect": "Allow", + "Action": [ + "trustedadvisor:*" + ], + "Resource": "*" + }, + { + "Sid": "OrganizationsTrustAdvisorReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:ListDelegatedAdministrators", + "organizations:EnableAWSServiceAccess", + "organizations:DisableAWSServiceAccess", + "organizations:RegisterDelegatedAdministrator", + "organizations:DeregisterDelegatedAdministrator" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "organizations:ServicePrincipal": [ + "reporting.trustedadvisor.amazonaws.com" + ] + } + } + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/reporting.trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisorReporting", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "reporting.trustedadvisor.amazonaws.com" + } + } + }, + { + "Sid": "OrganizationsHealthReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:EnableAWSServiceAccess", + "organizations:DisableAWSServiceAccess" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "organizations:ServicePrincipal": "health.amazonaws.com" + } + } + }, + { + "Sid": "AWSHealthReadOnly", + "Effect": "Allow", + "Action": [ + "health:*" + ], + "Resource": "*" + }, + { + "Sid": "OrganizationsReadOnly", + "Effect": "Allow", + "Action": [ + "organizations:ListParents", + "organizations:DescribeAccount", + "organizations:DescribeOrganization", + "organizations:DescribeOrganizationalUnit", + "organizations:ListAccounts", + "organizations:ListAWSServiceAccessForOrganization", + "organizations:ListDelegatedAdministrators" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "health.amazonaws.com" + } + } + }, + { + "Sid": "ServiceQuotasAccess", + "Effect": "Allow", + "Action": [ + "servicequotas:ListServices", + "servicequotas:ListServiceQuotas", + "servicequotas:ListAWSDefaultServiceQuotas", + "servicequotas:ListRequestedServiceQuotaChangeHistory", + "servicequotas:ListRequestedServiceQuotaChangeHistoryByQuota", + "servicequotas:ListTagsForResource", + "servicequotas:GetServiceQuota", + "servicequotas:GetAWSDefaultServiceQuota", + "servicequotas:GetRequestedServiceQuotaChange", + "servicequotas:RequestServiceQuotaIncrease", + "servicequotas:CreateSupportCase" + ], + "Resource": "*" + }, + { + "Sid": "SupportFullAccess", + "Effect": "Allow", + "Action": [ + "support:*", + "support-console:*" + ], + "Resource": "*" + } + , + { + "Sid": "IAMAccessAnalyzerFullAccess", + "Effect": "Allow", + "Action": [ + "access-analyzer:*" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "access-analyzer.amazonaws.com" + } + } + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_infrastructure_resilience.json b/infrastructure/stacks/account_policies/policies/rw_infrastructure_resilience.json new file mode 100644 index 00000000..0f11505f --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_infrastructure_resilience.json @@ -0,0 +1,49 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AWSResilienceHubFullAccess", + "Effect": "Allow", + "Action": [ + "backup:DescribeBackupVault", + "backup:GetBackupPlan", + "backup:GetBackupSelection", + "backup:ListBackupPlans", + "backup:ListBackupSelections", + "datasync:DescribeTask", + "datasync:ListLocations", + "datasync:ListTasks", + "devops-guru:ListMonitoredResources", + "dlm:GetLifecyclePolicies", + "dlm:GetLifecyclePolicy", + "drs:DescribeJobs", + "drs:DescribeSourceServers", + "drs:GetReplicationConfiguration", + "ds:DescribeDirectories", + "fis:GetExperiment", + "fis:GetExperimentTemplate", + "fis:ListExperimentTemplates", + "fis:ListExperiments", + "fis:ListExperimentResolvedTargets", + "fsx:DescribeFileSystems", + "route53-recovery-control-config:ListClusters", + "route53-recovery-control-config:ListControlPanels", + "route53-recovery-control-config:ListRoutingControls", + "route53-recovery-readiness:GetReadinessCheckStatus", + "route53-recovery-readiness:GetResourceSet", + "route53-recovery-readiness:ListReadinessChecks", + "servicecatalog:GetApplication", + "servicecatalog:ListAssociatedResources" + ], + "Resource": "*" + }, + { + "Sid": "AWSResilienceHubSSMStatement", + "Effect": "Allow", + "Action": [ + "ssm:GetParametersByPath" + ], + "Resource": "arn:aws:ssm:*:*:parameter/ResilienceHub/*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_infrastructure_security.json b/infrastructure/stacks/account_policies/policies/rw_infrastructure_security.json new file mode 100644 index 00000000..e53c9e98 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_infrastructure_security.json @@ -0,0 +1,126 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudTrailFullAccess", + "Effect": "Allow", + "Action": "cloudtrail:*", + "Resource": "*" + }, + { + "Sid": "SecurityHubFullAccess", + "Effect": "Allow", + "Action": "securityhub:*", + "Resource": "*" + }, + { + "Sid": "Inspector2AndCodeGuruSecurityFullAccess", + "Effect": "Allow", + "Action": [ + "inspector2:*", + "codeguru-security:*" + ], + "Resource": "*" + }, + { + "Sid": "GuardDutyFullAccess", + "Effect": "Allow", + "Action": [ + "guardduty:*" + ], + "Resource": "*" + }, + { + "Sid": "FirewallFullAccess", + "Effect": "Allow", + "Action": [ + "fms:*" + ], + "Resource": "*" + }, + { + "Sid": "KMSManagementFullAccess", + "Effect": "Allow", + "Action": [ + "kms:*" + ], + "Resource": "*" + }, + { + "Sid": "SecurityHubServiceLinkedRole", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "securityhub.amazonaws.com" + } + } + }, + { + "Sid": "CreateServiceLinkedRoleGuardDuty", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringLike": { + "iam:AWSServiceName": [ + "guardduty.amazonaws.com", + "malware-protection.guardduty.amazonaws.com" + ] + } + } + }, + { + "Sid": "AllowAccessToCreateInspectorSlr", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": [ + "agentless.inspector2.amazonaws.com", + "inspector2.amazonaws.com" + ] + } + } + }, + { + "Sid": "CreateSLRForACM", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/acm.amazonaws.com/AWSServiceRoleForCertificateManager*", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "acm.amazonaws.com" + } + } + }, + { + "Sid": "ManageSLRForACM", + "Effect": "Allow", + "Action": [ + "iam:DeleteServiceLinkedRole" + ], + "Resource": "arn:aws:iam::*:role/aws-service-role/acm.amazonaws.com/AWSServiceRoleForCertificateManager*" + }, + { + "Sid": "ShieldFullAccess", + "Effect": "Allow", + "Action": [ + "shield:*" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/shield.amazonaws.com/AWSServiceRoleForAWSShield", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "shield.amazonaws.com" + } + } + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_management.json b/infrastructure/stacks/account_policies/policies/rw_management.json new file mode 100644 index 00000000..c4b9f41a --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_management.json @@ -0,0 +1,162 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ApiGatewayFullAccess", + "Effect": "Allow", + "Action": [ + "apigateway:*" + ], + "Resource": "arn:aws:apigateway:*::/*" + }, + { + "Sid": "ApiGatewayDomainNameAccess", + "Effect": "Allow", + "Action": [ + "apigateway:GET" + ], + "Resource": "arn:aws:apigateway:*:*:/domainnameaccessassociations" + }, + { + "Sid": "ApiGatewayExecuteFullAccess", + "Effect": "Allow", + "Action": [ + "execute-api:Invoke", + "execute-api:ManageConnections" + ], + "Resource": "arn:aws:execute-api:*:*:*" + }, + { + "Sid": "EventBridgeAndSchedulerFullAccess", + "Effect": "Allow", + "Action": [ + "events:*", + "schemas:*", + "scheduler:*", + "pipes:*" + ], + "Resource": "*" + }, + { + "Sid": "IAMCreateServiceLinkedRoleForApiDestinations", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/apidestinations.events.amazonaws.com/AWSServiceRoleForAmazonEventBridgeApiDestinations", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "apidestinations.events.amazonaws.com" + } + } + }, + { + "Sid": "IAMCreateServiceLinkedRoleForAmazonEventBridgeSchemas", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/schemas.amazonaws.com/AWSServiceRoleForSchemas", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "schemas.amazonaws.com" + } + } + }, + { + "Sid": "IAMPassRoleAccessForEventBridge", + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::*:role/*", + "Condition": { + "StringLike": { + "iam:PassedToService": "events.amazonaws.com" + } + } + }, + { + "Sid": "IAMPassRoleAccessForScheduler", + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::*:role/*", + "Condition": { + "StringLike": { + "iam:PassedToService": "scheduler.amazonaws.com" + } + } + }, + { + "Sid": "IAMPassRoleAccessForPipes", + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::*:role/*", + "Condition": { + "StringLike": { + "iam:PassedToService": "pipes.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "ssm.amazonaws.com" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "iam:DeleteServiceLinkedRole" + ], + "Resource": "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*" + }, + { + "Sid": "SSMFullAccess", + "Effect": "Allow", + "Action": [ + "ssm:*" + ], + "Resource": "*" + }, + { + "Sid": "OpenSearchServiceFullAccess", + "Effect": "Allow", + "Action": [ + "es:*", + "osis:*", + "opensearch:*", + "aoss:*" + ], + "Resource": "*" + }, + { + "Sid": "SQSFullAccess", + "Effect": "Allow", + "Action": [ + "sqs:*" + ], + "Resource": "*" + }, + { + "Sid": "SNSFullAccess", + "Effect": "Allow", + "Action": "sns:*", + "Resource": "*" + }, + { + "Sid": "SecretsManagerFullAccess", + "Effect": "Allow", + "Action": [ + "secretsmanager:*" + ], + "Resource": "*" + }, + { + "Sid": "AppConfigFullAccess", + "Effect": "Allow", + "Action": [ + "appconfig:*" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_monitoring.json b/infrastructure/stacks/account_policies/policies/rw_monitoring.json new file mode 100644 index 00000000..fb7505c8 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_monitoring.json @@ -0,0 +1,48 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "CloudWatchFullAccess", + "Effect": "Allow", + "Action": [ + "application-autoscaling:*", + "application-signals:*", + "cloudwatch:*", + "logs:*", + "oam:ListSinks", + "rum:*", + "synthetics:*", + "xray:*" + ], + "Resource": "*" + }, + { + "Sid": "CloudWatchApplicationSignalsServiceLinkedRolePermissions", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/application-signals.cloudwatch.amazonaws.com/AWSServiceRoleForCloudWatchApplicationSignals", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "application-signals.cloudwatch.amazonaws.com" + } + } + }, + { + "Sid": "EventsServicePermissions", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/events.amazonaws.com/AWSServiceRoleForCloudWatchEvents*", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "events.amazonaws.com" + } + } + }, + { + "Sid": "OAMReadPermissions", + "Effect": "Allow", + "Action": "oam:ListAttachedLinks", + "Resource": "arn:aws:oam:*:*:sink/*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_networking.json b/infrastructure/stacks/account_policies/policies/rw_networking.json new file mode 100644 index 00000000..050c2a98 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_networking.json @@ -0,0 +1,37 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Route53FullAccess", + "Effect": "Allow", + "Action": [ + "route53:*", + "route53domains:*" + ], + "Resource": "*" + }, + { + "Sid": "AllowAssumeMgmtR53Role", + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/ftrs-directory-of-services-mgmt-domain-name-cross-account-access" + }, + { + "Sid": "ACMFullAccess", + "Effect": "Allow", + "Action": [ + "acm:*" + ], + "Resource": "*" + }, + { + "Sid": "CloudFrontFullAccess", + "Effect": "Allow", + "Action": [ + "cloudfront:*", + "cloudfront-keyvaluestore:*" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/policies/rw_security.json b/infrastructure/stacks/account_policies/policies/rw_security.json new file mode 100644 index 00000000..629f7b69 --- /dev/null +++ b/infrastructure/stacks/account_policies/policies/rw_security.json @@ -0,0 +1,179 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "IAMFullAccess", + "Effect": "Allow", + "Action": [ + "iam:GenerateCredentialReport", + "iam:List*", + "iam:GenerateServiceLastAccessedDetails", + "iam:TagRole", + "iam:DeletePolicy", + "iam:CreateRole", + "iam:DeleteRole", + "iam:AttachRolePolicy", + "iam:TagPolicy", + "iam:CreatePolicy", + "iam:PassRole", + "iam:Get*", + "iam:DetachRolePolicy", + "iam:SimulatePrincipalPolicy", + "iam:SimulateCustomPolicy", + "iam:CreatePolicyVersion", + "iam:DeletePolicyVersion", + "iam:TagOpenIDConnectProvider", + "iam:DeleteRolePolicy", + "iam:PutRolePolicy", + "iam:UpdateOpenIDConnectProviderThumbprint", + "iam:UntagPolicy", + "iam:UntagRole", + "iam:DeleteInstanceProfile" + ], + "Resource": "*" + }, + { + "Sid": "WAFClassicFullAccess", + "Effect": "Allow", + "Action": [ + "waf:*", + "waf-regional:*" + ], + "Resource": [ + "arn:aws:waf::*:bytematchset/*", + "arn:aws:waf::*:ipset/*", + "arn:aws:waf::*:ratebasedrule/*", + "arn:aws:waf::*:rule/*", + "arn:aws:waf::*:sizeconstraintset/*", + "arn:aws:waf::*:sqlinjectionset/*", + "arn:aws:waf::*:webacl/*", + "arn:aws:waf::*:xssmatchset/*", + "arn:aws:waf::*:regexmatch/*", + "arn:aws:waf::*:regexpatternset/*", + "arn:aws:waf::*:geomatchset/*", + "arn:aws:waf::*:rulegroup/*", + "arn:aws:waf:*:*:changetoken/*", + "arn:aws:waf-regional:*:*:bytematchset/*", + "arn:aws:waf-regional:*:*:ipset/*", + "arn:aws:waf-regional:*:*:ratebasedrule/*", + "arn:aws:waf-regional:*:*:rule/*", + "arn:aws:waf-regional:*:*:sizeconstraintset/*", + "arn:aws:waf-regional:*:*:sqlinjectionset/*", + "arn:aws:waf-regional:*:*:webacl/*", + "arn:aws:waf-regional:*:*:xssmatchset/*", + "arn:aws:waf-regional:*:*:regexmatch/*", + "arn:aws:waf-regional:*:*:regexpatternset/*", + "arn:aws:waf-regional:*:*:geomatchset/*", + "arn:aws:waf-regional:*:*:rulegroup/*", + "arn:aws:waf-regional:*:*:changetoken/*" + ] + }, + { + "Sid": "WAFV2FullAccess", + "Effect": "Allow", + "Action": [ + "wafv2:*" + ], + "Resource": [ + "arn:aws:wafv2:*:*:*/webacl/*/*", + "arn:aws:wafv2:*:*:*/ipset/*/*", + "arn:aws:wafv2:*:*:*/managedruleset/*/*", + "arn:aws:wafv2:*:*:*/rulegroup/*/*", + "arn:aws:wafv2:*:*:*/regexpatternset/*/*" + ] + }, + { + "Sid": "AllowDisassociateWebACL", + "Effect": "Allow", + "Action": [ + "wafv2:DisassociateWebACL" + ], + "Resource": "*" + }, + { + "Sid": "AllowActionsForAppSync", + "Effect": "Allow", + "Action": [ + "appsync:SetWebACL" + ], + "Resource": "arn:aws:appsync:*:*:apis/*" + }, + { + "Sid": "AllowListActionsForAppSync", + "Effect": "Allow", + "Action": [ + "appsync:ListGraphqlApis" + ], + "Resource": "*" + }, + { + "Sid": "AllowActionsForCognito", + "Effect": "Allow", + "Action": [ + "cognito-idp:AssociateWebACL", + "cognito-idp:DisassociateWebACL", + "cognito-idp:GetWebACLForResource" + ], + "Resource": "arn:aws:cognito-idp:*:*:userpool/*" + }, + { + "Sid": "AllowListActionsForCognito", + "Effect": "Allow", + "Action": [ + "cognito-idp:ListUserPools", + "cognito-idp:ListResourcesForWebACL" + ], + "Resource": "*" + }, + { + "Sid": "AllowActionsForAppRunner", + "Effect": "Allow", + "Action": [ + "apprunner:AssociateWebAcl", + "apprunner:DisassociateWebAcl", + "apprunner:DescribeWebAclForService" + ], + "Resource": "arn:aws:apprunner:*:*:service/*/*" + }, + { + "Sid": "AllowListActionsForAppRunner", + "Effect": "Allow", + "Action": [ + "apprunner:ListServices", + "apprunner:ListAssociatedServicesForWebAcl" + ], + "Resource": "*" + }, + { + "Sid": "AllowActionsForAmplify", + "Effect": "Allow", + "Action": [ + "amplify:AssociateWebACL", + "amplify:DisassociateWebACL", + "amplify:GetWebACLForResource" + ], + "Resource": "arn:aws:amplify:*:*:apps/*" + }, + { + "Sid": "AllowListActionsForAmplify", + "Effect": "Allow", + "Action": [ + "amplify:ListApps", + "amplify:ListResourcesForWebACL" + ], + "Resource": "*" + }, + { + "Sid": "AllowKMSDecryptDescribeKey", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:GenerateDataKey", + "kms:ListAliases", + "kms:ListKeys" + ], + "Resource": "*" + } + ] +} diff --git a/infrastructure/stacks/account_policies/variables.tf b/infrastructure/stacks/account_policies/variables.tf new file mode 100644 index 00000000..7c6b817d --- /dev/null +++ b/infrastructure/stacks/account_policies/variables.tf @@ -0,0 +1,4 @@ +variable "athena_output_bucket_name" { + description = "The name of the S3 bucket for Athena query results" + type = string +} From 771aa57089796a61c19e5902be7214f3addbea98 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 23 Jan 2026 15:39:13 +0000 Subject: [PATCH 014/181] Added JMeter to vale accept and fixed spacing issue in json --- .../account_policies/policies/rw_security.json | 16 ++++++++-------- .../styles/config/vocabularies/words/accept.txt | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/infrastructure/stacks/account_policies/policies/rw_security.json b/infrastructure/stacks/account_policies/policies/rw_security.json index 629f7b69..9c8b5e9e 100644 --- a/infrastructure/stacks/account_policies/policies/rw_security.json +++ b/infrastructure/stacks/account_policies/policies/rw_security.json @@ -164,14 +164,14 @@ "Resource": "*" }, { - "Sid": "AllowKMSDecryptDescribeKey", - "Effect": "Allow", - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:GenerateDataKey", - "kms:ListAliases", - "kms:ListKeys" + "Sid": "AllowKMSDecryptDescribeKey", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:GenerateDataKey", + "kms:ListAliases", + "kms:ListKeys" ], "Resource": "*" } diff --git a/scripts/config/vale/styles/config/vocabularies/words/accept.txt b/scripts/config/vale/styles/config/vocabularies/words/accept.txt index 5308c369..0e1f8499 100644 --- a/scripts/config/vale/styles/config/vocabularies/words/accept.txt +++ b/scripts/config/vale/styles/config/vocabularies/words/accept.txt @@ -33,3 +33,4 @@ Prepper dev Populator (?i)rollout +JMeter From 10df9d04b7f24b3b3c55d8c26e71d8507aa14768 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:52:37 +0000 Subject: [PATCH 015/181] fix: handle check file format issue --- .../account_policies/policies/rw_security.json | 16 ++++++++-------- scripts/config/vale/vale.ini | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/infrastructure/stacks/account_policies/policies/rw_security.json b/infrastructure/stacks/account_policies/policies/rw_security.json index 9c8b5e9e..7b573d61 100644 --- a/infrastructure/stacks/account_policies/policies/rw_security.json +++ b/infrastructure/stacks/account_policies/policies/rw_security.json @@ -164,14 +164,14 @@ "Resource": "*" }, { - "Sid": "AllowKMSDecryptDescribeKey", - "Effect": "Allow", - "Action": [ - "kms:Decrypt", - "kms:DescribeKey", - "kms:GenerateDataKey", - "kms:ListAliases", - "kms:ListKeys" + "Sid": "AllowKMSDecryptDescribeKey", + "Effect": "Allow", + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:GenerateDataKey", + "kms:ListAliases", + "kms:ListKeys" ], "Resource": "*" } diff --git a/scripts/config/vale/vale.ini b/scripts/config/vale/vale.ini index 57ae0bb7..b75ed223 100644 --- a/scripts/config/vale/vale.ini +++ b/scripts/config/vale/vale.ini @@ -8,4 +8,4 @@ Vocab = words BasedOnStyles = Vale [architecture/diagrams/**.md] -BasedOnStyles = \ No newline at end of file +BasedOnStyles = From aaf5ea492ffab3dad3560775806b08c405e2b2a9 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:56:04 +0000 Subject: [PATCH 016/181] fix: Add stack account_policies to plan and apply --- .github/workflows/pipeline-deploy-policies.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index e6be5614..5db4451b 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -72,7 +72,7 @@ jobs: include: - name: "account" environment: ${{ needs.metadata.outputs.account_type }} - stacks: "['github_runner']" + stacks: "['github_runner', 'account_policies']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} @@ -110,7 +110,7 @@ jobs: include: - name: "account" environment: ${{ needs.metadata.outputs.account_type }} - stacks: "['github_runner']" + stacks: "['github_runner', 'account_policies']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} From e820615a8eaf50d17b49662300d5a5c8b4cdc4d0 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:14:51 +0000 Subject: [PATCH 017/181] fix: Remove athena as it is not used --- infrastructure/stacks/account_policies/iam_policies.tf | 3 --- .../stacks/account_policies/policies/ro_data.json.tpl | 10 +--------- infrastructure/stacks/account_policies/variables.tf | 4 ---- 3 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 infrastructure/stacks/account_policies/variables.tf diff --git a/infrastructure/stacks/account_policies/iam_policies.tf b/infrastructure/stacks/account_policies/iam_policies.tf index 18508b1f..4b1868a9 100644 --- a/infrastructure/stacks/account_policies/iam_policies.tf +++ b/infrastructure/stacks/account_policies/iam_policies.tf @@ -16,9 +16,6 @@ locals { infrastructure_management_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_management.json")) infrastructure_management_ro_policy = jsondecode(file("${path.module}/policies/ro_infrastructure_management.json")) infrastructure_resilience_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_resilience.json")) - data_ro_policy = jsondecode(templatefile("${path.module}/policies/ro_data.json.tpl", { - athena_output_bucket_name = var.athena_output_bucket_name - })) } resource "aws_iam_policy" "billing_policy_ro" { diff --git a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl index 936952fe..636259bf 100644 --- a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl +++ b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl @@ -76,15 +76,7 @@ "s3-object-lambda:List*" ], "Resource": "*" - }, - { - "Sid": "S3ForAthena", - "Effect": "Allow", - "Action": [ - "s3:PutObject" - ], - "Resource": "arn:aws:s3:::${athena_output_bucket_name}/*" - }, + }, { "Sid": "AthenaLimitedAccess", "Effect": "Allow", diff --git a/infrastructure/stacks/account_policies/variables.tf b/infrastructure/stacks/account_policies/variables.tf deleted file mode 100644 index 7c6b817d..00000000 --- a/infrastructure/stacks/account_policies/variables.tf +++ /dev/null @@ -1,4 +0,0 @@ -variable "athena_output_bucket_name" { - description = "The name of the S3 bucket for Athena query results" - type = string -} From d398fa407b36a4c6cb7e585dd97bb9ca04a5df9d Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:22:18 +0000 Subject: [PATCH 018/181] fix: Remove trailing white space --- .../stacks/account_policies/policies/ro_data.json.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl index 636259bf..fd7b9254 100644 --- a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl +++ b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl @@ -76,7 +76,7 @@ "s3-object-lambda:List*" ], "Resource": "*" - }, + }, { "Sid": "AthenaLimitedAccess", "Effect": "Allow", From 8b272bce6ce031280e1869a72128cb03da10f6ef Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:27:59 +0000 Subject: [PATCH 019/181] fix: Add the data ro policy which was mistakenly deleted --- infrastructure/stacks/account_policies/iam_policies.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/account_policies/iam_policies.tf b/infrastructure/stacks/account_policies/iam_policies.tf index 4b1868a9..a923ec54 100644 --- a/infrastructure/stacks/account_policies/iam_policies.tf +++ b/infrastructure/stacks/account_policies/iam_policies.tf @@ -16,6 +16,7 @@ locals { infrastructure_management_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_management.json")) infrastructure_management_ro_policy = jsondecode(file("${path.module}/policies/ro_infrastructure_management.json")) infrastructure_resilience_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_resilience.json")) + data_ro_policy = jsondecode(templatefile("${path.module}/policies/ro_data.json.tpl")) } resource "aws_iam_policy" "billing_policy_ro" { From 6f3b9e34fa21d8644902d42a158a5d604f5429e3 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:45:11 +0000 Subject: [PATCH 020/181] fix: SAET-0000 check format issue --- .../policies/ro_data.json.tpl | 2 +- scripts/githooks/check-commit-message.sh | 67 +++++++++++++++++++ scripts/githooks/commit-msg | 12 ++++ scripts/tests/test.mk | 12 ++-- 4 files changed, 86 insertions(+), 7 deletions(-) create mode 100755 scripts/githooks/check-commit-message.sh create mode 100755 scripts/githooks/commit-msg diff --git a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl index fd7b9254..bd4c8c01 100644 --- a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl +++ b/infrastructure/stacks/account_policies/policies/ro_data.json.tpl @@ -76,7 +76,7 @@ "s3-object-lambda:List*" ], "Resource": "*" - }, + }, { "Sid": "AthenaLimitedAccess", "Effect": "Allow", diff --git a/scripts/githooks/check-commit-message.sh b/scripts/githooks/check-commit-message.sh new file mode 100755 index 00000000..994b3a3d --- /dev/null +++ b/scripts/githooks/check-commit-message.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +exit_code=0 +GIT_COMMIT_MESSAGE_MAX_LENGTH=100 + +function check_jira_ref { + BRANCH_NAME=$1 + COMMIT_MESSAGE=$2 + + HYPHENATED_BRANCH_NAME="${BRANCH_NAME//_/-}" + IFS='/' read -r -a name_array <<< "$HYPHENATED_BRANCH_NAME" + IFS='-' read -r -a ref <<< "${name_array[1]}" + JIRA_REF=$(echo "${ref[0]}"-"${ref[1]}" | tr '[:lower:]' '[:upper:]') + + # Add Jira ref after the colon, if missing + if [[ "$COMMIT_MESSAGE" =~ ^(feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|style)(\([a-z0-9_-]+\))?:[[:space:]] ]] && + [[ "$JIRA_REF" =~ ^(SAET)-[0-9]+$ ]] && + [[ "$COMMIT_MESSAGE" != *"$JIRA_REF"* ]]; then + COMMIT_MESSAGE=$(echo "$COMMIT_MESSAGE" | sed -E "s/^((feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|style)(\([a-z0-9_-]+\))?:)/\1 $JIRA_REF/") + fi + + echo $COMMIT_MESSAGE +} + +function check_commit_message_format { + COMMIT_MESSAGE="$1" + local REGEX='^(feat|fix|chore|docs|style|refactor|perf|test|ci|build|revert|style)(\([a-z0-9_-]+\))?: (SAET)-[0-9]+ .+' + + if ! [[ $COMMIT_MESSAGE =~ $REGEX ]]; then + echo -e "\033[0;31mInvalid conventional commit message format! Expected: (): \033[0m" + return 1 + fi +} + +function check_commit_message_length { + COMMIT_MESSAGE="$1" + COMMIT_MESSAGE_LENGTH="$(echo $COMMIT_MESSAGE | sed s/\'//g | head -1 | wc -m)" + + if [[ "$COMMIT_MESSAGE_LENGTH" -gt $GIT_COMMIT_MESSAGE_MAX_LENGTH ]] ; then + echo "At $COMMIT_MESSAGE_LENGTH characters the commit message exceeds limit of $GIT_COMMIT_MESSAGE_MAX_LENGTH" + fi +} + +function check_git_commit_message { + COMMIT_MESSAGE=$1 + + VALID_FORMAT=$(check_commit_message_format "$COMMIT_MESSAGE") + VALID_LENGTH=$(check_commit_message_length "$COMMIT_MESSAGE") + + if [[ ! -z "$VALID_LENGTH" || ! -z "$VALID_FORMAT" ]] ; then + [[ ! -z "$VALID_FORMAT" ]] && echo $VALID_FORMAT + [[ ! -z "$VALID_LENGTH" ]] && echo $VALID_LENGTH + return 1 + fi +} + +# ---- MAIN EXECUTION ---- +ORIGINAL_COMMIT_MESSAGE=${COMMIT_MESSAGE:-"$(cat $1)"} +BRANCH_NAME=${BRANCH_NAME:-$(git rev-parse --abbrev-ref HEAD)} +COMMIT_MESSAGE=$(check_jira_ref "$BRANCH_NAME" "$ORIGINAL_COMMIT_MESSAGE") + +# Update commit message file +sed -i -e "s/$ORIGINAL_COMMIT_MESSAGE/$COMMIT_MESSAGE/g" $1 + +check_git_commit_message "$(cat $1)" +exit_code=$? +exit $exit_code diff --git a/scripts/githooks/commit-msg b/scripts/githooks/commit-msg new file mode 100755 index 00000000..64c095a0 --- /dev/null +++ b/scripts/githooks/commit-msg @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Script to check the commit log message contains jira reference +# at start and inserts if not +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. + +# This script is copied to the .git/hooks directory by the make config target + +scripts/githooks/check-commit-message.sh $* diff --git a/scripts/tests/test.mk b/scripts/tests/test.mk index 42c6593a..4fd72b06 100644 --- a/scripts/tests/test.mk +++ b/scripts/tests/test.mk @@ -76,12 +76,12 @@ test: # Run all the test tasks @Testing test-load _test: - set -e - script="./scripts/tests/${name}.sh" - if [ -e "$${script}" ]; then - exec $${script} - else - echo "make test-${name} not implemented: $${script} not found" >&2 + set -e; \ + script="./scripts/tests/${name}.sh"; \ + if [ -e "$${script}" ]; then \ + exec $${script}; \ + else \ + echo "make test-${name} not implemented: $${script} not found" >&2; \ fi ${VERBOSE}.SILENT: \ From efa17b3d17b965b4fce69715c3fce0d6f0d31138 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:50:04 +0000 Subject: [PATCH 021/181] fix: SAET-0000 replace template with simple json logic --- infrastructure/stacks/account_policies/iam_policies.tf | 2 +- .../policies/{ro_data.json.tpl => ro_data.json} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename infrastructure/stacks/account_policies/policies/{ro_data.json.tpl => ro_data.json} (100%) diff --git a/infrastructure/stacks/account_policies/iam_policies.tf b/infrastructure/stacks/account_policies/iam_policies.tf index a923ec54..ccc10512 100644 --- a/infrastructure/stacks/account_policies/iam_policies.tf +++ b/infrastructure/stacks/account_policies/iam_policies.tf @@ -16,7 +16,7 @@ locals { infrastructure_management_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_management.json")) infrastructure_management_ro_policy = jsondecode(file("${path.module}/policies/ro_infrastructure_management.json")) infrastructure_resilience_rw_policy = jsondecode(file("${path.module}/policies/rw_infrastructure_resilience.json")) - data_ro_policy = jsondecode(templatefile("${path.module}/policies/ro_data.json.tpl")) + data_ro_policy = jsondecode(file("${path.module}/policies/ro_data.json")) } resource "aws_iam_policy" "billing_policy_ro" { diff --git a/infrastructure/stacks/account_policies/policies/ro_data.json.tpl b/infrastructure/stacks/account_policies/policies/ro_data.json similarity index 100% rename from infrastructure/stacks/account_policies/policies/ro_data.json.tpl rename to infrastructure/stacks/account_policies/policies/ro_data.json From f5bd0e23637c2c4b34f8830d799d2a8d0c00eea2 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:34:20 +0000 Subject: [PATCH 022/181] fix: SAET-0000 Remove unwanted stacks for now --- .../workflows/pipeline-deploy-account-infrastructure.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 76c9420a..5fe1b663 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -87,10 +87,7 @@ jobs: stacks: "['account_security']" - name: "env" environment: ${{ needs.metadata.outputs.environment }} - stacks: "['terraform_management','account_wide','domain_name']" - - name: "mgmt" - environment: ${{ needs.metadata.outputs.mgmt_environment }} - stacks: "['terraform_management','account_security']" + stacks: "['terraform_management']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} From 0e7115f47c3d7475662b3c51cc6c08b05bca2227 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:38:53 +0000 Subject: [PATCH 023/181] fix: SAET-0000 Add account security stack tfvars --- infrastructure/.gitignore | 1 + infrastructure/environments/dev/account_security.tfvars | 1 + 2 files changed, 2 insertions(+) create mode 100644 infrastructure/environments/dev/account_security.tfvars diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index 8c639d11..3cdfd67d 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -22,6 +22,7 @@ crash.*.log !common.tfvars !environment.tfvars !github_runner.tfvars +!account_security.tfvars # Ignore override files as they are usually used to override resources locally and so # are not checked in diff --git a/infrastructure/environments/dev/account_security.tfvars b/infrastructure/environments/dev/account_security.tfvars new file mode 100644 index 00000000..cd2da3ef --- /dev/null +++ b/infrastructure/environments/dev/account_security.tfvars @@ -0,0 +1 @@ +enable_iam_analyzer = true From a12246d7d1514b61c625f8e4b65202645fd2af8f Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:58:29 +0000 Subject: [PATCH 024/181] fix: SAET-0000 Add tfvars for terraform management --- infrastructure/.gitignore | 1 + infrastructure/terraform_management.tfvars | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 infrastructure/terraform_management.tfvars diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index 3cdfd67d..6de44923 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -23,6 +23,7 @@ crash.*.log !environment.tfvars !github_runner.tfvars !account_security.tfvars +!terraform_management.tfvars # Ignore override files as they are usually used to override resources locally and so # are not checked in diff --git a/infrastructure/terraform_management.tfvars b/infrastructure/terraform_management.tfvars new file mode 100644 index 00000000..1bc18b6e --- /dev/null +++ b/infrastructure/terraform_management.tfvars @@ -0,0 +1,2 @@ +s3_versioning = true +s3_logging_bucket_versioning = false From 826440eef6e503ee3eb3a8e4febfa1ac099a5b0e Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Sat, 24 Jan 2026 00:05:05 +0000 Subject: [PATCH 025/181] fix: SAET-0000 Add for testing the pipeline --- .github/workflows/pipeline-deploy-account-infrastructure.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 5fe1b663..bae1b852 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -100,7 +100,7 @@ jobs: manual-approval: name: "Manual approval for ${{ needs.metadata.outputs.environment }} infrastructure deployment" - if: ${{ github.ref == 'refs/heads/main' && (needs.plan-infrastructure.outputs.plan_result == 'true') }} + if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up') && (needs.plan-infrastructure.outputs.plan_result == 'true') }} needs: - metadata - plan-infrastructure @@ -115,7 +115,7 @@ jobs: concurrency: group: "${{ matrix.environment }}-default-${{ matrix.name }}-${{matrix.stacks}}" cancel-in-progress: false - if: github.ref == 'refs/heads/main' + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up' }} needs: - metadata - manual-approval From 68eefc3a06f20714e5e43bd20ef664fe9c751ed7 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:57:07 +0000 Subject: [PATCH 026/181] fix: SAET-0000 Handle create service linked role error --- .../account_github_runner_policy.json.tpl | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl index 47df7b42..fd77a61b 100644 --- a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl +++ b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl @@ -48,6 +48,28 @@ } } }, + { + "Sid": "AllowAccessAnalyzerServiceLinkedRoleCreation", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/access-analyzer.amazonaws.com/AWSServiceRoleForAccessAnalyzer", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "access-analyzer.amazonaws.com" + } + } + }, + { + "Sid": "AllowShieldServiceLinkedRoleCreation", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/shield.amazonaws.com/AWSServiceRoleForAWSShield", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "shield.amazonaws.com" + } + } + }, { "Sid": "MonitoringFullAccess", "Effect": "Allow", @@ -109,8 +131,7 @@ "arn:aws:iam::*:instance-profile/${repo_name}-*", "arn:aws:iam::*:role/dms-vpc-role", "arn:aws:iam::*:role/${project}-*", - "arn:aws:iam::*:policy/${project}-*", - "arn:aws:iam::*:role/aws-service-role/shield.amazonaws.com/AWSServiceRoleForAWSShield" + "arn:aws:iam::*:policy/${project}-*" ] }, { From 7b0b80242b1f2e8b7deed068cd53f4ceb408f810 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 27 Jan 2026 08:58:54 +0000 Subject: [PATCH 027/181] Adding the account wide stack --- ...ipeline-deploy-account-infrastructure.yaml | 7 +- infrastructure/stacks/account_wide/README.md | 77 ++++++ .../stacks/account_wide/cloudwatch.tf | 72 ++++++ infrastructure/stacks/account_wide/data.tf | 39 +++ .../stacks/account_wide/ec2_performance.tf | 147 +++++++++++ infrastructure/stacks/account_wide/kms.tf | 118 +++++++++ .../account_wide/opensearch_collection.tf | 12 + infrastructure/stacks/account_wide/s3.tf | 121 +++++++++ infrastructure/stacks/account_wide/secrets.tf | 56 +++++ .../stacks/account_wide/security_group.tf | 89 +++++++ .../stacks/account_wide/ssm_parameters.tf | 59 +++++ .../templates/performance_user_data.sh.tmpl | 174 +++++++++++++ .../stacks/account_wide/variables.tf | 234 ++++++++++++++++++ infrastructure/stacks/account_wide/vpc.tf | 182 ++++++++++++++ infrastructure/stacks/account_wide/vpce.tf | 62 +++++ infrastructure/stacks/account_wide/vpn.tf | 100 ++++++++ infrastructure/stacks/account_wide/waf.tf | 203 +++++++++++++++ 17 files changed, 1749 insertions(+), 3 deletions(-) create mode 100644 infrastructure/stacks/account_wide/README.md create mode 100644 infrastructure/stacks/account_wide/cloudwatch.tf create mode 100644 infrastructure/stacks/account_wide/data.tf create mode 100644 infrastructure/stacks/account_wide/ec2_performance.tf create mode 100644 infrastructure/stacks/account_wide/kms.tf create mode 100644 infrastructure/stacks/account_wide/opensearch_collection.tf create mode 100644 infrastructure/stacks/account_wide/s3.tf create mode 100644 infrastructure/stacks/account_wide/secrets.tf create mode 100644 infrastructure/stacks/account_wide/security_group.tf create mode 100644 infrastructure/stacks/account_wide/ssm_parameters.tf create mode 100644 infrastructure/stacks/account_wide/templates/performance_user_data.sh.tmpl create mode 100644 infrastructure/stacks/account_wide/variables.tf create mode 100644 infrastructure/stacks/account_wide/vpc.tf create mode 100644 infrastructure/stacks/account_wide/vpce.tf create mode 100644 infrastructure/stacks/account_wide/vpn.tf create mode 100644 infrastructure/stacks/account_wide/waf.tf diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index bae1b852..ba71e45c 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -15,6 +15,7 @@ on: - "infrastructure/modules/s3/**" - "infrastructure/stacks/account_security/**" - "infrastructure/modules/shield/**" + - "infrastructure/stacks/account_wide/**" workflow_run: workflows: ["Pipeline Deploy Policies Infrastructure"] types: @@ -65,7 +66,7 @@ jobs: tag: ${{ inputs.tag }} environment: ${{ needs.metadata.outputs.environment }} workspace: "default" - stacks: "['terraform_management','account_security']" + stacks: "['terraform_management','account_security','account_wide']" type: account build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} secrets: inherit @@ -87,7 +88,7 @@ jobs: stacks: "['account_security']" - name: "env" environment: ${{ needs.metadata.outputs.environment }} - stacks: "['terraform_management']" + stacks: "['terraform_management','account_wide']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} @@ -128,7 +129,7 @@ jobs: stacks: "['account_security']" - name: "env" environment: ${{ needs.metadata.outputs.environment }} - stacks: "['terraform_management']" + stacks: "['terraform_management','account_wide']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} diff --git a/infrastructure/stacks/account_wide/README.md b/infrastructure/stacks/account_wide/README.md new file mode 100644 index 00000000..181ee8d4 --- /dev/null +++ b/infrastructure/stacks/account_wide/README.md @@ -0,0 +1,77 @@ +# Account-Wide Infrastructure + +Infrastructure that is deployed once per environment inside an account. + +> Note: Deploy this stack using the `default` workspace. + +This stack provisions: + +1. IAM role for GitHub Actions (via OIDC) +2. Environment wide VPC, including public, private, and database subnets +3. A performance EC2 host for Apache `JMeter`–based testing + +--- + +## Performance EC2 (Apache `JMeter`) + +A single Amazon Linux 2023 EC2 instance in a private `subnet` for performance testing. Access is through AWS Systems Manager Session Manager (no inbound SSH). On first boot, user data installs Java, Apache `JMeter`, a minimal plugin set, an optional JWT library, and a convenience wrapper. + +### What this stack creates + +- EC2 instance in the first private `subnet` of the environment wide VPC +- Dedicated security group with minimal egress + - TCP 443 to 0.0.0.0/0 (HTTPS; required for downloads and APIs; private subnets egress via NAT) + - UDP 53 to the VPC Route 53 Resolver only (CIDR: `${cidrhost(var.vpc["cidr"], 2)}/32`) (DNS) + - UDP 123 to public NTP (CIDR: `0.0.0.0/0`) to allow time sync when link-local IPs cannot be referenced in code +- Dedicated NACLs with minimal DNS and NTP allowances (because NACLs are stateless) + - DNS: Outbound UDP 53 to the VPC resolver (`${cidrhost(var.vpc["cidr"], 2)}/32`), and inbound UDP 32768–65535 from the resolver + - NTP: Outbound UDP 123 to public NTP (`0.0.0.0/0`), and inbound UDP 32768–65535 from the internet (broader; see note below) +- IAM role and instance profile attached to the instance + - Managed policy: `AmazonSSMManagedInstanceCore` +- On first boot, user data installs: + - Java 17 (`Amazon Corretto` with OpenJDK fallback) and base tools + - Apache `JMeter` (from archive.apache.org) under `/opt/jmeter/current` + - Apache `JMeter` Plugin Manager 1.11 and `cmdrunner` 2.3 + - Default plugins: `jpgc-graphs-basic` and `jpgc-graphs-additional` + - JWT library JAR (version configurable) + - Symlinks: `/usr/local/bin/jmeter` and `/usr/local/bin/jmeter-server` + - Wrapper: `/usr/local/bin/jmeter-run` + - SSM Agent installed and enabled (falls back to regional S3 RPM if needed) + - Logs written to `/var/log/user-data.log` + +### Variables (selected) + +- `performance_instance_type` (string, default `t3.small`) +- `performance_volume_size` (number, default `30`; must be ≥ 30 GiB) +- `performance_version` (string, Apache `JMeter` version, default `5.6.3`) +- `performance_poweroff_after_setup` (true/false, default `true`) — power off after install to avoid idle cost +- `performance_ami_name_pattern` (list(string), default `['al2023-ami-*-x86_64']`) +- `performance_ami_architectures` (list(string), default `['x86_64']`) +- `performance_jwt_dependency_version` (string, default `4.5.0`) + +Set these in your environment tfvars, for example `infrastructure/environments/dev/account_wide.tfvars`. + +### Prerequisites + +- Outbound internet access from private subnets (typically via a NAT Gateway) so the instance can download Apache `JMeter`, plugins, and the JWT JAR +- Alternatively, configure VPC interface endpoints for SSM/SSMMessages/EC2Messages if operating without NAT +- The account-level GitHub runner role needs permission to create and pass the instance role/profile (configured centrally in this repository) + +### Usage + +- Plan and apply this stack with the account-wide tfvars and your environment tfvars +- After apply, connect using Session Manager from the AWS Console or CLI +- Validate installation on the instance: + - Run `jmeter -v` or execute tests with `jmeter-run` as needed + - Inspect `/var/log/user-data.log` for provisioning details + +### Notes on NTP configuration + +- Best practice is to restrict NTP to the AWS-provided link-local time sync endpoint, but where referencing a literal IP in code is prohibited, this configuration uses `0.0.0.0/0` for UDP 123 egress and the corresponding inbound ephemeral range in NACLs +- Consider tightening to the AWS time sync endpoint in a follow-up if policy allows referencing link-local IPs in infra code + +### Troubleshooting + +- If Apache `JMeter`, plugins, or the JWT JAR are missing, check `/var/log/user-data.log` and confirm outbound access +- Confirm the SSM Agent is running: `systemctl status amazon-ssm-agent` +- `JAVA_HOME` and `JMETER_HOME` are exported in profile scripts; the `jmeter-run` wrapper also sets them if missing diff --git a/infrastructure/stacks/account_wide/cloudwatch.tf b/infrastructure/stacks/account_wide/cloudwatch.tf new file mode 100644 index 00000000..0f9c3a61 --- /dev/null +++ b/infrastructure/stacks/account_wide/cloudwatch.tf @@ -0,0 +1,72 @@ +# AWS WAF CloudWatch Log Group Resource Policy +resource "aws_cloudwatch_log_resource_policy" "waf_log_group_policy" { + policy_document = data.aws_iam_policy_document.waf_log_group_policy_document.json + policy_name = "${local.resource_prefix}-${var.waf_log_group_policy_name}" + provider = aws.us-east-1 +} + +data "aws_iam_policy_document" "waf_log_group_policy_document" { + version = "2012-10-17" + statement { + effect = "Allow" + principals { + identifiers = ["delivery.logs.amazonaws.com"] + type = "Service" + } + actions = ["logs:CreateLogStream", "logs:PutLogEvents"] + resources = [ + "arn:aws:logs:${var.aws_region_us_east_1}:${data.aws_caller_identity.current.account_id}:log-group:aws-waf-logs-ftrs-dos-${var.environment}*:log-stream:*" + ] + condition { + test = "ArnLike" + values = ["arn:aws:logs:${var.aws_region_us_east_1}:${data.aws_caller_identity.current.account_id}:*"] + variable = "aws:SourceArn" + } + condition { + test = "StringEquals" + values = [tostring(data.aws_caller_identity.current.account_id)] + variable = "aws:SourceAccount" + } + } + provider = aws.us-east-1 +} + +# AWS OSIS & API Gateway CloudWatch Log Group Resource Policy +resource "aws_cloudwatch_log_resource_policy" "osis_apigw_log_group_policy" { + policy_document = data.aws_iam_policy_document.osis_apigw_log_group_policy_document.json + policy_name = "${local.resource_prefix}-${var.osis_apigw_log_group_policy_name}" +} + +data "aws_iam_policy_document" "osis_apigw_log_group_policy_document" { + version = "2012-10-17" + statement { + effect = "Allow" + principals { + identifiers = ["delivery.logs.amazonaws.com"] + type = "Service" + } + actions = ["logs:CreateLogStream", "logs:PutLogEvents"] + resources = [ + "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/vendedlogs/OpenSearchIngestion/dynamodb-to-os-${var.environment}*:log-stream:*", + "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/ftrs-dos-${var.environment}*/default:log-stream:*" + ] + condition { + test = "ArnLike" + values = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"] + variable = "aws:SourceArn" + } + condition { + test = "StringEquals" + values = [tostring(data.aws_caller_identity.current.account_id)] + variable = "aws:SourceAccount" + } + } +} + +resource "aws_cloudwatch_log_group" "waf_log_group" { + # checkov:skip=CKV_AWS_158: Justification: Using AWS default encryption. + name = "${var.waf_log_group_name_prefix}${local.resource_prefix}-${var.waf_log_group}" + retention_in_days = var.waf_log_group_retention_days + log_group_class = var.waf_log_group_class + provider = aws.us-east-1 +} diff --git a/infrastructure/stacks/account_wide/data.tf b/infrastructure/stacks/account_wide/data.tf new file mode 100644 index 00000000..f3f85d35 --- /dev/null +++ b/infrastructure/stacks/account_wide/data.tf @@ -0,0 +1,39 @@ +data "aws_acm_certificate" "vpn_cert" { + count = var.environment == "dev" ? 1 : 0 + + domain = "${local.account_prefix}-vpn" + types = ["IMPORTED"] + statuses = ["ISSUED"] + most_recent = true +} + +data "aws_availability_zones" "available_azs" { + state = "available" + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +data "aws_ami" "al2023" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = var.performance_ami_name_pattern + } + filter { + name = "architecture" + values = var.performance_ami_architectures + } + filter { + name = "root-device-type" + values = ["ebs"] + } +} + +data "aws_subnet" "vpc_private_subnets_by_count" { + count = length(module.vpc.private_subnets) + id = module.vpc.private_subnets[count.index] +} diff --git a/infrastructure/stacks/account_wide/ec2_performance.tf b/infrastructure/stacks/account_wide/ec2_performance.tf new file mode 100644 index 00000000..0fc42811 --- /dev/null +++ b/infrastructure/stacks/account_wide/ec2_performance.tf @@ -0,0 +1,147 @@ +// Performance EC2 instance(s) for performance testing in the account-wide stack. +// Creates a small Amazon Linux 2023 instance in a private subnet, reachable via SSM (Session Manager) only. +// Installs Apache JMeter on first boot and powers off the instance when installation completes (configurable). + + +locals { + # Name tag for Performance EC2 instance, scoped to this stack + performance_name = "${local.account_prefix}-performance" +} + +resource "aws_instance" "performance" { + ami = data.aws_ami.al2023.id + instance_type = var.performance_instance_type + subnet_id = element(module.vpc.private_subnets, 0) + vpc_security_group_ids = [aws_security_group.performance_ec2_sg.id] + iam_instance_profile = aws_iam_instance_profile.ec2_performance_instance_profile.name + associate_public_ip_address = false + ebs_optimized = true + monitoring = true + + user_data = templatefile("${path.module}/templates/performance_user_data.sh.tmpl", { + aws_region = var.aws_region, + performance_version = var.performance_version, + performance_poweroff_after_setup = var.performance_poweroff_after_setup, + performance_jwt_dependency_version = var.performance_jwt_dependency_version + }) + user_data_replace_on_change = true + + root_block_device { + encrypted = true + volume_size = var.performance_volume_size + volume_type = "gp3" + } + + metadata_options { + http_tokens = "required" + http_put_response_hop_limit = 1 + } + + instance_initiated_shutdown_behavior = "stop" + + tags = { + Name = local.performance_name + Role = "performance" + } + + + depends_on = [ + aws_iam_role_policy_attachment.ec2_performance_ssm_core + ] +} + +# IAM role and instance profile for Performance EC2 +resource "aws_iam_role" "ec2_performance_role" { + name = "${local.account_prefix}-ec2-performance" + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { Service = "ec2.amazonaws.com" } + }] + }) +} + +resource "aws_iam_role_policy_attachment" "ec2_performance_ssm_core" { + role = aws_iam_role.ec2_performance_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_instance_profile" "ec2_performance_instance_profile" { + name = "${local.account_prefix}-instance-profile-performance" + role = aws_iam_role.ec2_performance_role.name +} + +# S3 access required for Performance EC2 (explicit performance buckets only) +data "aws_iam_policy_document" "ec2_performance_s3" { + statement { + sid = "AllowS3BucketMetadataForPerformance" + actions = [ + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:ListBucketMultipartUploads" + ] + resources = [ + module.performance_s3.s3_bucket_arn + ] + } + + statement { + sid = "AllowS3ObjectAccessForPerformance" + actions = [ + "s3:GetObject", + "s3:GetObjectVersion", + "s3:PutObject", + "s3:DeleteObject", + "s3:CreateMultipartUpload", + "s3:UploadPart", + "s3:ListMultipartUploadParts", + "s3:CompleteMultipartUpload", + "s3:AbortMultipartUpload" + ] + resources = [ + "${module.performance_s3.s3_bucket_arn}/*" + ] + } +} + +resource "aws_iam_role_policy" "ec2_performance_s3" { + name = "${local.account_prefix}-ec2-performance-s3" + role = aws_iam_role.ec2_performance_role.id + policy = data.aws_iam_policy_document.ec2_performance_s3.json +} + +# Secrets Manager read access restricted to explicit secrets used by performance tests. To be expanded +data "aws_iam_policy_document" "ec2_performance_secrets" { + statement { + sid = "AllowGetExplicitPerformanceSecrets" + actions = [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ] + resources = [ + aws_secretsmanager_secret.api_jmeter_pks_key[0].arn, + aws_secretsmanager_secret.api_ca_cert_secret[0].arn, + aws_secretsmanager_secret.api_ca_pk_secret[0].arn + ] + } + + statement { + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + resources = [ + module.secrets_manager_encryption_key.arn + ] + } +} + +resource "aws_iam_role_policy" "ec2_performance_secrets" { + name = "${local.account_prefix}-ec2-performance-secrets" + role = aws_iam_role.ec2_performance_role.id + policy = data.aws_iam_policy_document.ec2_performance_secrets.json +} diff --git a/infrastructure/stacks/account_wide/kms.tf b/infrastructure/stacks/account_wide/kms.tf new file mode 100644 index 00000000..47e78cf9 --- /dev/null +++ b/infrastructure/stacks/account_wide/kms.tf @@ -0,0 +1,118 @@ +module "sqs_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.sqs + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "sqs.amazonaws.com" + description = "Encryption key for SQS queues in ${var.environment} environment" +} + +module "secrets_manager_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.secrets_manager + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "secretsmanager.amazonaws.com" + description = "Encryption key for Secrets Manager in ${var.environment} environment" + + additional_policy_statements = [ + { + Sid = "AllowEC2SecretsAccess" + Effect = "Allow" + Principal = { + AWS = [ + aws_iam_role.ec2_performance_role.arn + ] + } + Action = [ + "kms:Decrypt", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + Resource = "*" + }, + { + Sid = "AllowGitHubRunnerAccess" + Effect = "Allow" + Principal = { + AWS = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.app_github_runner_role_name}", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.account_github_runner_role_name}" + ] + } + Action = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + Resource = "*" + } + ] +} + +module "opensearch_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.opensearch + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "opensearchservice.amazonaws.com" + description = "Encryption key for OpenSearch in ${var.environment} environment" +} + +module "rds_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.rds + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "rds.amazonaws.com" + description = "Encryption key for RDS instances in ${var.environment} environment" +} + +module "dynamodb_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.dynamodb + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "dynamodb.amazonaws.com" + description = "Encryption key for DynamoDB tables in ${var.environment} environment" +} + +module "dms_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.dms + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "dms.amazonaws.com" + description = "Encryption key for DMS in ${var.environment} environment" +} + +module "ssm_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.ssm + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "ssm.amazonaws.com" + description = "Encryption key for SSM parameters in ${var.environment} environment" + + additional_policy_statements = [ + { + Sid = "AllowGitHubRunnerAccess" + Effect = "Allow" + Principal = { + AWS = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.app_github_runner_role_name}", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.account_github_runner_role_name}" + ] + } + Action = [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:DescribeKey" + ] + Resource = "*" + } + ] +} + +module "s3_encryption_key" { + source = "../../modules/kms" + alias_name = local.kms_aliases.s3 + account_id = data.aws_caller_identity.current.account_id + aws_service_name = "s3.amazonaws.com" + description = "Encryption key for S3 buckets in ${var.environment} environment" +} diff --git a/infrastructure/stacks/account_wide/opensearch_collection.tf b/infrastructure/stacks/account_wide/opensearch_collection.tf new file mode 100644 index 00000000..b8acd39d --- /dev/null +++ b/infrastructure/stacks/account_wide/opensearch_collection.tf @@ -0,0 +1,12 @@ +module "opensearch_serverless" { + # Module version: 1.7.0 + source = "git::https://github.com/terraform-aws-modules/terraform-aws-opensearch.git//modules/collection?ref=6f7113edebb53779de7225634f7e914bc6d59c8c" + + name = "${local.project_prefix}${var.opensearch_collection_name}" + description = "OpenSearch Serverless collection for DynamoDB ingestion" + type = var.opensearch_type + standby_replicas = var.opensearch_standby_replicas + + create_access_policy = var.opensearch_create_access_policy + create_network_policy = var.opensearch_create_network_policy +} diff --git a/infrastructure/stacks/account_wide/s3.tf b/infrastructure/stacks/account_wide/s3.tf new file mode 100644 index 00000000..64490569 --- /dev/null +++ b/infrastructure/stacks/account_wide/s3.tf @@ -0,0 +1,121 @@ +# VPC Flow Logs S3 Bucket and Resource Policy +module "vpc_flow_logs_s3_bucket" { + source = "../../modules/s3" + bucket_name = "${local.resource_prefix}-${var.vpc_flow_logs_bucket_name}" + versioning = var.flow_log_s3_versioning + force_destroy = var.flow_log_s3_force_destroy + lifecycle_rule_inputs = [ + { + id = "delete_logs_older_than_x_days" + enabled = true + filter = { + prefix = "" + } + expiration = { + days = var.flow_logs_s3_expiration_days + } + } + ] + s3_logging_bucket = local.s3_logging_bucket +} + +resource "aws_s3_bucket_policy" "vpc_flow_logs_s3_bucket_policy" { + bucket = module.vpc_flow_logs_s3_bucket.s3_bucket_id + policy = data.aws_iam_policy_document.vpc_flow_logs_s3_bucket_policy_doc.json +} + +data "aws_iam_policy_document" "vpc_flow_logs_s3_bucket_policy_doc" { + statement { + sid = "AWSLogDeliveryWrite" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = ["s3:PutObject"] + + resources = ["${module.vpc_flow_logs_s3_bucket.s3_bucket_arn}/*"] + } + + statement { + sid = "AWSLogDeliveryAclCheck" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = ["s3:GetBucketAcl"] + + resources = [module.vpc_flow_logs_s3_bucket.s3_bucket_arn] + } +} + +# Subnet Flow Logs S3 Bucket and Resource Policy +module "subnet_flow_logs_s3_bucket" { + source = "../../modules/s3" + bucket_name = "${local.resource_prefix}-${var.subnet_flow_logs_bucket_name}" + versioning = var.flow_log_s3_versioning + force_destroy = var.flow_log_s3_force_destroy + lifecycle_rule_inputs = [ + { + id = "delete_logs_older_than_x_days" + enabled = true + filter = { + prefix = "" + } + expiration = { + days = var.flow_logs_s3_expiration_days + } + } + ] +} + +resource "aws_s3_bucket_policy" "subnet_flow_logs_s3_bucket_policy" { + bucket = module.subnet_flow_logs_s3_bucket.s3_bucket_id + policy = data.aws_iam_policy_document.subnet_flow_logs_s3_bucket_policy_doc.json +} + +data "aws_iam_policy_document" "subnet_flow_logs_s3_bucket_policy_doc" { + statement { + sid = "AWSLogDeliveryWrite" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = ["s3:PutObject"] + + resources = ["${module.subnet_flow_logs_s3_bucket.s3_bucket_arn}/*"] + } + + statement { + sid = "AWSLogDeliveryAclCheck" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = ["s3:GetBucketAcl"] + + resources = [module.subnet_flow_logs_s3_bucket.s3_bucket_arn] + } +} + +module "trust_store_s3_bucket" { + # This module creates an S3 bucket for the trust store used for MTLS Certificates. + source = "../../modules/s3" + bucket_name = local.s3_trust_store_bucket_name + s3_logging_bucket = local.s3_logging_bucket + s3_encryption_key_arn = module.s3_encryption_key.arn + enable_kms_encryption = var.enable_s3_kms_encryption +} + +# IS Performance S3 Bucket +module "performance_s3" { + source = "../../modules/s3" + bucket_name = "${local.resource_prefix}-${var.performance_files_bucket_name}" +} diff --git a/infrastructure/stacks/account_wide/secrets.tf b/infrastructure/stacks/account_wide/secrets.tf new file mode 100644 index 00000000..76a0f8d5 --- /dev/null +++ b/infrastructure/stacks/account_wide/secrets.tf @@ -0,0 +1,56 @@ +resource "aws_secretsmanager_secret" "api_ca_cert_secret" { + # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.repo_name}/${var.environment}/api-ca-cert" + description = "Public certificate for mTLS authentication" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "api_ca_pk_secret" { + # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.repo_name}/${var.environment}/api-ca-pk" + description = "Private key for mTLS authentication" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "cis2_private_key" { + # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.project}/${var.environment}/cis2-private-key" + description = "Private key for CIS2 in ${var.environment} environment" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "cis2_public_key" { + # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.project}/${var.environment}/cis2-public-key" + description = "Public key for CIS2 in ${var.environment} environment in JWKS format" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + + +resource "aws_secretsmanager_secret" "api_jmeter_pks_key" { + # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.repo_name}/${var.environment}/api-jmeter-pks-key" + description = "Private key for jmeter mTLS authentication" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "dos_search_proxygen_jwt_credentials" { + # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.project}/${var.environment}/dos-search-proxygen-jwt-credentials" + description = "JWT credentials for DOS Search Proxygen in ${var.environment} environment" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "dos_search_jwt_credentials" { + # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. + count = local.is_primary_environment ? 1 : 0 + name = "/${var.project}/${var.environment}/dos-search/jwt-credentials" + description = "JWT credentials for NHS Digital Onboarding test application in ${var.environment} environment" + kms_key_id = module.secrets_manager_encryption_key.key_id +} diff --git a/infrastructure/stacks/account_wide/security_group.tf b/infrastructure/stacks/account_wide/security_group.tf new file mode 100644 index 00000000..7b3f606a --- /dev/null +++ b/infrastructure/stacks/account_wide/security_group.tf @@ -0,0 +1,89 @@ +resource "aws_security_group" "vpce_rds_security_group" { + name = "${local.account_prefix}-current-dos-rds-vpc-endpoint-sg" + description = "Security group for VPC Endpoint to RDS (DMS)" + vpc_id = module.vpc.vpc_id +} + +resource "aws_vpc_security_group_ingress_rule" "vpce_allow_all_ingress" { + security_group_id = aws_security_group.vpce_rds_security_group.id + referenced_security_group_id = aws_security_group.dms_replication_security_group.id + description = "Allow ingress from DMS replication instance" + ip_protocol = "tcp" + from_port = var.rds_port + to_port = var.rds_port +} + +# trivy:ignore:aws-vpc-no-public-egress-sgr : TODO https://nhsd-jira.digital.nhs.uk/browse/FTRS-386 +resource "aws_vpc_security_group_egress_rule" "vpce_allow_all_egress" { + description = "Allow all outbound traffic to RDS" + security_group_id = aws_security_group.vpce_rds_security_group.id + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "tcp" + from_port = var.rds_port + to_port = var.rds_port +} + +# Security group for Performance EC2 instance +resource "aws_security_group" "performance_ec2_sg" { + name = "${local.account_prefix}-performance-sg" + description = "Security group for Performance EC2 instance (SSM-managed)" + vpc_id = module.vpc.vpc_id + + tags = { + Name = "${local.resource_prefix}-performance-sg" + } +} + +# HTTPS egress for software installation, AWS APIs, and performance tests +# Note: 0.0.0.0/0 here still egresses via a NAT Gateway from private subnets; no inbound exposure. +# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required HTTPS egress to the internet for installs and AWS APIs; SG egress is least-privilege and NACLs restrict UDP +resource "aws_vpc_security_group_egress_rule" "performance_egress_https" { + security_group_id = aws_security_group.performance_ec2_sg.id + description = "Allow HTTPS egress (tcp/443) to the internet for installs, AWS APIs, and tests" + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "tcp" + from_port = 443 + to_port = 443 +} + +# DNS egress (UDP 53) to VPC resolver only (base+2 of VPC CIDR) +resource "aws_vpc_security_group_egress_rule" "performance_egress_dns_udp" { + security_group_id = aws_security_group.performance_ec2_sg.id + description = "Allow DNS egress (udp/53) to VPC resolver only" + cidr_ipv4 = format("%s/32", cidrhost(var.vpc["cidr"], 2)) + ip_protocol = "udp" + from_port = 53 + to_port = 53 +} + +# NTP egress (UDP 123) to public NTP servers when link-local IP cannot be used +# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required NTP/UDP 123 egress because link-local IPs cannot be referenced in this environment; NACLs restrict inbound UDP ephemeral responses +resource "aws_vpc_security_group_egress_rule" "performance_egress_ntp_udp" { + security_group_id = aws_security_group.performance_ec2_sg.id + description = "Allow NTP egress (udp/123) to public NTP servers" + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "udp" + from_port = 123 + to_port = 123 +} + + +# HTTP egress for software installation, AWS APIs, and performance tests +# Note: 0.0.0.0/0 here still egresses via a NAT Gateway from private subnets; no inbound exposure. +# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required HTTPS egress to the internet for installs and AWS APIs; SG egress is least-privilege and NACLs restrict UDP +resource "aws_vpc_security_group_egress_rule" "performance_egress_http" { + security_group_id = aws_security_group.performance_ec2_sg.id + description = "Allow HTTP egress (tcp/80) to the internet for installs" + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "tcp" + from_port = 80 + to_port = 80 +} + +resource "aws_security_group" "dms_replication_security_group" { + # checkov:skip=CKV2_AWS_5:Works locally in the checkov checks. Also is used in the file above + name = "${local.resource_prefix}-etl-replication-sg" + description = "Security group for DMS ETL replication instance" + vpc_id = module.vpc.vpc_id + depends_on = [module.vpc] +} diff --git a/infrastructure/stacks/account_wide/ssm_parameters.tf b/infrastructure/stacks/account_wide/ssm_parameters.tf new file mode 100644 index 00000000..b50bb95d --- /dev/null +++ b/infrastructure/stacks/account_wide/ssm_parameters.tf @@ -0,0 +1,59 @@ +resource "aws_ssm_parameter" "dos_aws_account_id_mgmt" { + name = "/dos/${var.environment}/aws_account_id_mgmt" + description = "Id of the management account" + type = "SecureString" + tier = "Standard" + value = "default" + key_id = module.ssm_encryption_key.arn + + lifecycle { + ignore_changes = [ + value + ] + } +} + +resource "aws_ssm_parameter" "texas_vpc_endpoint_service_name" { + name = "/${local.resource_prefix}/texas-vpc-endpoint-service-name" + description = "The VPC Endpoint Service Name for the Texas RDS instance" + type = "SecureString" + tier = "Standard" + value = "changeme" # Placeholder, to be manually updated in AWS Console or via CLI later + key_id = module.ssm_encryption_key.arn + + lifecycle { + ignore_changes = [ + value + ] + } +} + +resource "aws_ssm_parameter" "cis2_client_config" { + name = "/${var.project}/${var.environment}/cis2-client-config" + description = "The CIS2 Client Configuration" + type = "SecureString" + tier = "Standard" + value = "CHANGE_ME" # Placeholder, to be manually updated in AWS Console or via CLI later + key_id = module.ssm_encryption_key.arn + + lifecycle { + ignore_changes = [ + value + ] + } +} + +resource "aws_ssm_parameter" "cis2_connection_manager" { + name = "/${var.project}/${var.environment}/cis2-connection-manager" + description = "The CIS2 Connection Manager Configuration" + type = "SecureString" + tier = "Standard" + value = "CHANGE_ME" # Placeholder, to be manually updated in AWS Console or via CLI later + key_id = module.ssm_encryption_key.arn + + lifecycle { + ignore_changes = [ + value + ] + } +} diff --git a/infrastructure/stacks/account_wide/templates/performance_user_data.sh.tmpl b/infrastructure/stacks/account_wide/templates/performance_user_data.sh.tmpl new file mode 100644 index 00000000..f91b2ca3 --- /dev/null +++ b/infrastructure/stacks/account_wide/templates/performance_user_data.sh.tmpl @@ -0,0 +1,174 @@ +#!/bin/bash +set -euo pipefail + +# Log user-data to file as in gp_search +exec > >(tee -a /var/log/user-data.log) 2>&1 + +REGION="${aws_region}" +JVER="${performance_version}" +POWEROFF="${performance_poweroff_after_setup}" +JWT_VER="${performance_jwt_dependency_version}" + +: "$${JAVA_HOME:=}" # Predefine to avoid 'JAVA_HOME: unbound variable' under set -u + +# Update base packages (non-fatal if repos are temporarily unavailable) +if command -v dnf >/dev/null 2>&1; then + dnf -y update || true +elif command -v yum >/dev/null 2>&1; then + yum -y update || true +fi + +# Install Java 17 and base tools with fallback (dnf then yum) +BASE_PKGS="unzip jq curl wget tar openssh-clients make" +if command -v dnf >/dev/null 2>&1; then + dnf -y install $BASE_PKGS || true + dnf -y install java-17-amazon-corretto-headless || true + if ! command -v java >/dev/null 2>&1; then + dnf -y install java-17-openjdk-headless || true + fi +elif command -v yum >/dev/null 2>&1; then + yum -y install $BASE_PKGS || true + yum -y install java-17-amazon-corretto-headless || true + if ! command -v java >/dev/null 2>&1; then + yum -y install java-17-openjdk-headless || true + fi +fi + +# Install AWS CLI v2 (prefer package manager, fallback to official zip) +if ! command -v aws >/dev/null 2>&1; then + if command -v dnf >/dev/null 2>&1; then + dnf -y install awscli || true + elif command -v yum >/dev/null 2>&1; then + yum -y install awscli || true + fi +fi +if ! command -v aws >/dev/null 2>&1; then + TMPDIR=$(mktemp -d) + pushd "$TMPDIR" >/dev/null + curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip -q awscliv2.zip + ./aws/install --update + popd >/dev/null + rm -rf "$TMPDIR" +fi + +# Set JAVA_HOME system-wide if java is present +if command -v java >/dev/null 2>&1; then + JB=$(readlink -f "$(command -v java)" 2>/dev/null || command -v java) + JH=$(dirname "$(dirname "$JB")") + cat >/etc/profile.d/java_home.sh </etc/profile.d/aws_default_region.sh </etc/profile.d/jmeter.sh <<'ENVJ' +export JMETER_HOME=/opt/jmeter/apache-jmeter +export PATH="$JMETER_HOME/bin:$PATH" +ENVJ +chmod 0644 /etc/profile.d/jmeter.sh + +JM_HOME="$${JM_BASE}/current" + +# Ensure SSM Agent is installed and running with fallback to S3 RPM +if command -v dnf >/dev/null 2>&1; then + dnf -y install amazon-ssm-agent || true +elif command -v yum >/dev/null 2>&1; then + yum -y install amazon-ssm-agent || true +fi +if ! rpm -q amazon-ssm-agent >/dev/null 2>&1; then + rpm -Uvh --force "https://s3.$${REGION}.amazonaws.com/amazon-ssm-$${REGION}/latest/linux_amd64/amazon-ssm-agent.rpm" || true +fi +systemctl enable amazon-ssm-agent || true +systemctl restart amazon-ssm-agent || systemctl start amazon-ssm-agent || true + +# Always install JMeter Plugin Manager, cmdrunner, and a sensible default plugin set +PLUGINS_MANAGER_VERSION=1.11 +CMDRUNNER_VERSION=2.3 +DEFAULT_PLUGINS="jpgc-graphs-basic,jpgc-graphs-additional,jpgc-tst,jpgc-casutg" + +mkdir -p "$${JM_HOME}/lib/ext" "$${JM_HOME}/lib" +curl -fL -o "$${JM_HOME}/lib/ext/jmeter-plugins-manager-$${PLUGINS_MANAGER_VERSION}.jar" \ + "https://repo1.maven.org/maven2/kg/apc/jmeter-plugins-manager/$${PLUGINS_MANAGER_VERSION}/jmeter-plugins-manager-$${PLUGINS_MANAGER_VERSION}.jar" +curl -fL -o "$${JM_HOME}/lib/cmdrunner-$${CMDRUNNER_VERSION}.jar" \ + "https://repo1.maven.org/maven2/kg/apc/cmdrunner/$${CMDRUNNER_VERSION}/cmdrunner-$${CMDRUNNER_VERSION}.jar" + +# Setup PluginManagerCMD and install default plugins +java -cp "$${JM_HOME}/lib/ext/jmeter-plugins-manager-$${PLUGINS_MANAGER_VERSION}.jar" \ + org.jmeterplugins.repository.PluginManagerCMDInstaller || true +bash "$${JM_HOME}/bin/PluginsManagerCMD.sh" install "$${DEFAULT_PLUGINS}" || true + +# Always install JWT dependency (used by IS_Proxy_Test_Plan) +mkdir -p "$${JM_HOME}/lib" +curl -fL -o "$${JM_HOME}/lib/java-jwt-$${JWT_VER}.jar" \ + "https://repo1.maven.org/maven2/com/auth0/java-jwt/$${JWT_VER}/java-jwt-$${JWT_VER}.jar" || true + +# Provide convenience symlinks like gp_search +ln -sf /opt/jmeter/apache-jmeter/bin/jmeter /usr/local/bin/jmeter +ln -sf /opt/jmeter/apache-jmeter/bin/jmeter-server /usr/local/bin/jmeter-server + +# Always install a convenience wrapper for non-interactive runs +cat >/usr/local/bin/jmeter-run <<'WRAP' +#!/usr/bin/env bash +set -euo pipefail +# Ensure JAVA_HOME if not set +if [[ -z "$${JAVA_HOME:-}" ]]; then + if command -v java >/dev/null 2>&1; then + JB=$(readlink -f "$(command -v java)" 2>/dev/null || command -v java) + export JAVA_HOME="$(dirname "$(dirname "$JB")")" + export PATH="$JAVA_HOME/bin:$PATH" + fi +fi +# Ensure JMETER_HOME if not set +if [[ -z "$${JMETER_HOME:-}" ]]; then + export JMETER_HOME="/opt/jmeter/current" + export PATH="$JMETER_HOME/bin:$PATH" +fi +if ! command -v jmeter >/dev/null 2>&1; then + echo "jmeter not found in PATH" >&2 + exit 1 +fi +exec jmeter "$@" +WRAP +chmod +x /usr/local/bin/jmeter-run + +# Print JMeter version +jmeter -v || true + +# Optionally power off after setup to avoid idle costs +if [[ "$${POWEROFF}" == "true" ]]; then + shutdown -h +1 "Powering off after JMeter installation" +fi diff --git a/infrastructure/stacks/account_wide/variables.tf b/infrastructure/stacks/account_wide/variables.tf new file mode 100644 index 00000000..51ac0589 --- /dev/null +++ b/infrastructure/stacks/account_wide/variables.tf @@ -0,0 +1,234 @@ +variable "enable_nat_gateway" { + description = "Whether to create a NAT Gateway for the VPC" + type = bool +} + +variable "single_nat_gateway" { + description = "Whether to use a single NAT Gateway in the VPC" + type = bool +} + +variable "one_nat_gateway_per_az" { + description = "Whether to create only one NAT Gateway per AZ" + type = bool +} + +variable "create_database_subnet_group" { + description = "Whether to create a database subnet group for RDS" + type = bool +} + +variable "create_database_route_table" { + description = "Whether to create a database route table for RDS" + type = bool +} + +variable "create_database_internet_gateway_route" { + description = "Whether to create an internet gateway route for public database access" + type = bool +} + +variable "create_database_nat_gateway_route" { + description = "Whether to create a NAT gateway route for the database" + type = bool +} + +variable "log_group_retention_in_days" { + description = "Number of days to retain logs" + default = 7 +} + +variable "opensearch_type" { + description = "The type of OpenSearch" + type = string +} + +variable "opensearch_standby_replicas" { + description = "Number of standby replicas for OpenSearch" + type = string +} + +variable "opensearch_create_access_policy" { + description = "Flag to create access policy for OpenSearch" + type = bool +} + +variable "opensearch_create_network_policy" { + description = "Flag to create network policy for OpenSearch" + type = bool +} + +variable "opensearch_collection_name" { + description = "The OpenSearch Collection name" + type = string +} + +variable "waf_log_group_policy_name" { + description = "The WAF log group policy name" + type = string +} + +variable "osis_apigw_log_group_policy_name" { + description = "The OSIS & API Gateway log group policy name" + type = string +} + +variable "gateway_vpc_endpoint_type" { + description = "The VPC enpoint type" + type = string + default = "Gateway" +} + +variable "database_dedicated_network_acl" { + description = "Whether to use dedicated network ACL (not default) and custom rules for database subnets" + type = bool +} + +variable "private_dedicated_network_acl" { + description = "Whether to use dedicated network ACL (not default) and custom rules for private subnets" + type = bool +} + +variable "public_dedicated_network_acl" { + description = "Whether to use dedicated network ACL (not default) and custom rules for public subnets" + type = bool +} + +variable "flow_log_destination_type" { + description = "THe destination type for the flow logs" + type = string +} + +variable "flow_log_file_format" { + description = "The file format for the flow logs" + type = string +} + +variable "vpc_flow_logs_bucket_name" { + description = "The VPC Flow logs bucket name" + type = string +} + +variable "subnet_flow_logs_bucket_name" { + description = "The Subnet Flow logs bucket name" + type = string +} + +variable "flow_log_s3_versioning" { + description = "Whether to enable versioning on the S3 bucket" + type = bool +} + +variable "flow_log_s3_force_destroy" { + description = "Whether to forcefully destroy the bucket when it contains objects" + type = bool + default = false +} + +variable "flow_logs_s3_expiration_days" { + description = "The number of days before the VPC flow logs are deleted" + type = number +} + +variable "vpc" { + description = "A map of VPC configuration, including VPC ID, CIDR block, and other networking details" + type = map(any) + default = {} +} + +variable "enable_flow_log" { + description = "Whether VPC Flow logs are enabled or not" + type = bool + default = false +} + +variable "waf_name" { + description = "The Web ACL name for WAF" + type = string +} + +variable "waf_scope" { + description = "The scope for WAF" + type = string +} + +variable "waf_log_group" { + description = "Name for the WAF Web ACL log group" + type = string +} +variable "waf_log_group_class" { + description = "The log group class for WAF" + type = string +} + +variable "waf_log_group_name_prefix" { + description = "Prefix for WAF CloudWatch Log Group Name" + type = string + default = "aws-waf-logs-" +} + +variable "waf_log_group_retention_days" { + description = "The retention period for the Read only viewer Web ACL Log group" + type = number + default = 365 +} + +# Performance EC2 configuration +variable "performance_instance_type" { + description = "EC2 instance type for performance testing" + type = string + default = "c7a.xlarge" +} + +variable "performance_ami_name_pattern" { + description = "List of AMI name patterns to match for the performance EC2 (e.g., AL2023)" + type = list(string) + default = ["al2023-ami-*-x86_64"] +} + +variable "performance_ami_architectures" { + description = "List of acceptable CPU architectures for the performance EC2 AMI" + type = list(string) + default = ["x86_64"] +} + +variable "performance_volume_size" { + description = "Root EBS volume size (GiB) for the performance instance" + type = number + default = 30 + validation { + condition = var.performance_volume_size >= 30 + error_message = "performance_volume_size must be >= 30 GiB to satisfy the AMI snapshot minimum size" + } +} + +variable "performance_version" { + description = "Apache JMeter version to install" + type = string + default = "5.6.3" +} + +variable "performance_poweroff_after_setup" { + description = "Whether to power off the instance after installing JMeter" + type = bool + default = true +} + +variable "performance_jwt_dependency_version" { + description = "Version of java-jwt library to download" + type = string + default = "4.5.0" +} + +# Performance S3 bucket names +variable "performance_files_bucket_name" { + description = "S3 bucket name for performance files" + type = string + default = "is-performance-files-bucket" +} + +variable "enable_s3_kms_encryption" { + description = "Whether to enable KMS encryption for S3 buckets" + type = bool + default = false +} diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf new file mode 100644 index 00000000..94e04a23 --- /dev/null +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -0,0 +1,182 @@ +# trivy:ignore:aws-vpc-no-public-ingress-acl : TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-575 +# trivy:ignore:aws-autoscaling-enable-at-rest-encryption : TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-575 +module "vpc" { + # Module version: 6.0.1 + source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git?ref=a0307d4d1807de60b3868b96ef1b369808289157" + + name = "${local.account_prefix}-${var.vpc["name"]}" + cidr = var.vpc["cidr"] + enable_nat_gateway = var.enable_nat_gateway + single_nat_gateway = var.single_nat_gateway + one_nat_gateway_per_az = var.one_nat_gateway_per_az + + create_database_subnet_group = var.create_database_subnet_group + create_database_subnet_route_table = var.create_database_route_table + create_database_internet_gateway_route = var.create_database_internet_gateway_route + create_database_nat_gateway_route = var.create_database_nat_gateway_route + database_subnet_group_name = "${local.account_prefix}-database-subnet-group" + + azs = slice(data.aws_availability_zones.available_azs.names, 0, 3) + public_subnets = local.public_subnets + private_subnets = local.private_subnets + database_subnets = local.database_subnets + + # NACL configuration + database_dedicated_network_acl = var.database_dedicated_network_acl + database_inbound_acl_rules = local.network_acls["default_inbound"] + database_outbound_acl_rules = local.network_acls["default_outbound"] + + private_dedicated_network_acl = var.private_dedicated_network_acl + private_inbound_acl_rules = local.network_acls["default_inbound"] + private_outbound_acl_rules = local.network_acls["default_outbound"] + + public_dedicated_network_acl = var.public_dedicated_network_acl + public_inbound_acl_rules = local.network_acls["default_inbound"] + public_outbound_acl_rules = local.network_acls["default_outbound"] + + # VPC Flow Logs + enable_flow_log = var.enable_flow_log + flow_log_destination_type = var.flow_log_destination_type + flow_log_destination_arn = module.vpc_flow_logs_s3_bucket.s3_bucket_arn + flow_log_file_format = var.flow_log_file_format + + # Manage Default NACL rules for the VPC + default_network_acl_ingress = [] + default_network_acl_egress = [] +} + +locals { + + public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] + private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"], var.vpc["private_subnet_d"], var.vpc["private_subnet_e"], var.vpc["private_subnet_f"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] + database_subnets = [var.vpc["database_subnet_a"], var.vpc["database_subnet_b"], var.vpc["database_subnet_c"]] + vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] + + network_acls = { + + default_inbound = [ + { + rule_number = 900 + rule_action = "allow" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_block = "0.0.0.0/0" + }, + { + rule_number = 901 + rule_action = "allow" + from_port = 0 + to_port = 65535 + protocol = "tcp" + ipv6_cidr_block = "::/0" + }, + # DNS responses: Allow inbound UDP 32768–65535 only from the VPC resolver (base+2). + # Matches the outbound UDP 53 rule below. Required because NACLs are stateless and + # responses target an ephemeral source port on the instance. + { + rule_number = 902 + rule_action = "allow" + from_port = 32768 + to_port = 65535 + protocol = "udp" + cidr_block = format("%s/32", cidrhost(var.vpc["cidr"], 2)) + }, + # NTP responses: Allow inbound UDP 32768–65535 from the internet to support public NTP servers. + # Note: Broader than using the Amazon Time Sync IP; preferred only if a literal IP cannot be used. + # trivy:ignore:aws-vpc-no-public-ingress-acl : NTP inbound ephemeral (UDP 32768–65535) required for time sync responses + { + rule_number = 903 + rule_action = "allow" + from_port = 32768 + to_port = 65535 + protocol = "udp" + cidr_block = "0.0.0.0/0" + } + ] + + default_outbound = [ + { + rule_number = 900 + rule_action = "allow" + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_block = "0.0.0.0/0" + }, + { + rule_number = 901 + rule_action = "allow" + from_port = 0 + to_port = 65535 + protocol = "tcp" + ipv6_cidr_block = "::/0" + }, + # DNS: Allow outbound UDP 53 only to the VPC resolver (base+2). + # Required for name resolution (e.g., SSM endpoints). Matching inbound UDP responses + # are permitted by the inbound ephemeral rule above (32768–65535 from the resolver). + { + rule_number = 902 + rule_action = "allow" + from_port = 53 + to_port = 53 + protocol = "udp" + cidr_block = format("%s/32", cidrhost(var.vpc["cidr"], 2)) + }, + # NTP: Allow outbound UDP 123 to the internet to support public NTP servers (e.g., time.aws.com, pool.ntp.org). + # Note: Broader than using the Amazon Time Sync IP; preferred only if a literal IP cannot be used. + # trivy:ignore:aws-vpc-no-public-egress-acl : NTP egress (UDP 123) required to reach public NTP servers when link-local IP is disallowed + { + rule_number = 903 + rule_action = "allow" + from_port = 123 + to_port = 123 + protocol = "udp" + cidr_block = "0.0.0.0/0" + } + ] + } +} + +resource "aws_flow_log" "public_subnet_flow_log_s3" { + count = length(local.public_subnets) + log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn + log_destination_type = var.flow_log_destination_type + traffic_type = "REJECT" + destination_options { + per_hour_partition = true + } + subnet_id = module.vpc.public_subnets[count.index] +} + +resource "aws_flow_log" "private_subnet_flow_log_s3" { + count = length(local.private_subnets) + log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn + log_destination_type = var.flow_log_destination_type + traffic_type = "REJECT" + destination_options { + per_hour_partition = true + } + subnet_id = module.vpc.private_subnets[count.index] +} + +resource "aws_flow_log" "database_subnet_flow_log_s3" { + count = length(local.database_subnets) + log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn + log_destination_type = var.flow_log_destination_type + traffic_type = "REJECT" + destination_options { + per_hour_partition = true + } + subnet_id = module.vpc.database_subnets[count.index] +} + +# Add a CIDR Range tag to the private subnets for filtering +resource "aws_ec2_tag" "private_subnet_tags" { + count = length(module.vpc.private_subnets) + + resource_id = data.aws_subnet.vpc_private_subnets_by_count[count.index].id + key = "CidrRange" + value = split("/", data.aws_subnet.vpc_private_subnets_by_count[count.index].cidr_block)[1] +} + diff --git a/infrastructure/stacks/account_wide/vpce.tf b/infrastructure/stacks/account_wide/vpce.tf new file mode 100644 index 00000000..71a757cd --- /dev/null +++ b/infrastructure/stacks/account_wide/vpce.tf @@ -0,0 +1,62 @@ +resource "aws_vpc_endpoint" "dynamodb_vpce" { + vpc_id = module.vpc.vpc_id + service_name = "com.amazonaws.${var.aws_region}.dynamodb" + vpc_endpoint_type = var.gateway_vpc_endpoint_type + route_table_ids = module.vpc.private_route_table_ids + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "AllowAccessFromVPC", + Effect = "Allow", + Principal = "*", + Action = "dynamodb:*", + Resource = "*", + Condition = { + StringEquals = { + "aws:SourceVpc" : module.vpc.vpc_id + } + } + }, + { + Sid = "DenyAccessFromOutsideVPC", + Effect = "Deny", + Principal = "*", + Action = "*", + Resource = "*", + Condition = { + StringNotEquals = { + "aws:SourceVpc" : module.vpc.vpc_id + } + } + } + ] + }) + + tags = { + Name = "${local.resource_prefix}-dynamodb-gateway-vpc-endpoint" + } +} + +resource "aws_vpc_endpoint" "s3_vpce" { + vpc_id = module.vpc.vpc_id + service_name = "com.amazonaws.${var.aws_region}.s3" + vpc_endpoint_type = var.gateway_vpc_endpoint_type + route_table_ids = module.vpc.private_route_table_ids + tags = { + Name = "${local.resource_prefix}-s3-gateway-vpc-endpoint" + } +} + +resource "aws_vpc_endpoint" "current_dos_rds_endpoint" { + vpc_id = module.vpc.vpc_id + service_name = aws_ssm_parameter.texas_vpc_endpoint_service_name.value + vpc_endpoint_type = "Interface" + subnet_ids = [module.vpc.private_subnets[0], module.vpc.private_subnets[1], module.vpc.private_subnets[2]] + security_group_ids = [aws_security_group.vpce_rds_security_group.id] + + tags = { + Name = "${local.resource_prefix}-current-dos-rds-vpc-endpoint" + } +} diff --git a/infrastructure/stacks/account_wide/vpn.tf b/infrastructure/stacks/account_wide/vpn.tf new file mode 100644 index 00000000..b2485069 --- /dev/null +++ b/infrastructure/stacks/account_wide/vpn.tf @@ -0,0 +1,100 @@ +resource "aws_ec2_client_vpn_endpoint" "vpn" { + count = var.environment == "dev" ? 1 : 0 + + description = "${local.account_prefix}-vpn" + vpc_id = module.vpc.vpc_id + client_cidr_block = var.vpc["vpn_subnet"] + server_certificate_arn = data.aws_acm_certificate.vpn_cert[0].arn + + security_group_ids = [aws_security_group.vpn_security_group[0].id] + + transport_protocol = "tcp" + split_tunnel = true + + authentication_options { + type = "certificate-authentication" + root_certificate_chain_arn = data.aws_acm_certificate.vpn_cert[0].arn + } + + connection_log_options { + enabled = true + cloudwatch_log_group = aws_cloudwatch_log_group.vpn_log_group[0].name + cloudwatch_log_stream = aws_cloudwatch_log_stream.vpn_log_stream[0].name + } +} + +resource "aws_ec2_client_vpn_network_association" "database_association" { + count = var.environment == "dev" ? length(module.vpc.database_subnets) : 0 + + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn[0].id + subnet_id = module.vpc.database_subnets[count.index] +} + +resource "aws_ec2_client_vpn_authorization_rule" "vpn_authorization_rule" { + for_each = var.environment == "dev" ? { for s in module.vpc.database_subnet_objects : s.id => s } : {} + + client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn[0].id + target_network_cidr = each.value.cidr_block + authorize_all_groups = true +} + +resource "aws_security_group" "vpn_security_group" { + count = var.environment == "dev" ? 1 : 0 + name = "${local.account_prefix}-vpn-sg" + description = "Security Group for Client VPN Endpoint" + vpc_id = module.vpc.vpc_id + + ingress { + protocol = "tcp" + from_port = 443 + to_port = 443 + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + protocol = "tcp" + from_port = 443 + to_port = 443 + cidr_blocks = module.vpc.private_subnets_cidr_blocks + } + + egress { + protocol = "tcp" + from_port = 80 + to_port = 80 + cidr_blocks = module.vpc.private_subnets_cidr_blocks + } + + egress { + protocol = "tcp" + from_port = 5432 + to_port = 5432 + cidr_blocks = module.vpc.database_subnets_cidr_blocks + } +} + +resource "aws_cloudwatch_log_group" "vpn_log_group" { + count = var.environment == "dev" ? 1 : 0 + name = "${local.account_prefix}-vpn-log-group" + retention_in_days = var.log_group_retention_in_days +} + +resource "aws_cloudwatch_log_stream" "vpn_log_stream" { + count = var.environment == "dev" ? 1 : 0 + name = "${local.account_prefix}-vpn-log-stream" + log_group_name = aws_cloudwatch_log_group.vpn_log_group[0].name +} + +resource "aws_secretsmanager_secret" "vpn_ca_cert_secret" { + # checkov:skip=CKV2_AWS_57: Justification: This is generated manually for dev environment only. + count = var.environment == "dev" ? 1 : 0 + name = "/${var.repo_name}/${var.environment}/vpn-ca-cert" + kms_key_id = module.secrets_manager_encryption_key.key_id +} + +resource "aws_secretsmanager_secret" "vpn_ca_pk_secret" { + # checkov:skip=CKV2_AWS_57: Justification: This is generated manually for dev environment only. + count = var.environment == "dev" ? 1 : 0 + name = "/${var.repo_name}/${var.environment}/vpn-ca-pk" + kms_key_id = module.secrets_manager_encryption_key.key_id +} diff --git a/infrastructure/stacks/account_wide/waf.tf b/infrastructure/stacks/account_wide/waf.tf new file mode 100644 index 00000000..515fe409 --- /dev/null +++ b/infrastructure/stacks/account_wide/waf.tf @@ -0,0 +1,203 @@ +resource "aws_wafv2_web_acl" "waf_web_acl" { + name = "${local.resource_prefix}-${var.waf_name}" + description = "WAF Web ACL" + scope = var.waf_scope + + default_action { + allow {} + } + + rule { + name = "allowed-countries-rule" + priority = 11 + action { + block {} + } + statement { + not_statement { + statement { + geo_match_statement { + country_codes = ["GB", "JE", "IM"] + } + } + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "allowed-countries-rule" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesAmazonIpReputationList" + priority = 21 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAmazonIpReputationList" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesAnonymousIpList" + priority = 31 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAnonymousIpList" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAnonymousIpList" + sampled_requests_enabled = true + } + } + + rule { + name = "rate-limit-rule" + priority = 41 + + action { + block {} + } + + statement { + rate_based_statement { + limit = 100 + aggregate_key_type = "FORWARDED_IP" + evaluation_window_sec = 120 + forwarded_ip_config { + fallback_behavior = "NO_MATCH" + header_name = "X-Forwarded-For" + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "rate-limit-rule" + sampled_requests_enabled = true + } + } + rule { + name = "AWSManagedRulesCommonRuleSet" + priority = 51 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesCommonRuleSet" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesKnownBadInputsRuleSet" + priority = 61 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesKnownBadInputsRuleSet" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesAdminProtectionRuleSet" + priority = 71 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAdminProtectionRuleSet" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAdminProtectionRuleSet" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesBotControlRuleSet" + priority = 81 + + override_action { + none {} + } + + statement { + managed_rule_group_statement { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesBotControlRuleSet" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.resource_prefix}-metric" + sampled_requests_enabled = true + } + + provider = aws.us-east-1 +} + +resource "aws_wafv2_web_acl_logging_configuration" "waf_logging_configuration" { + log_destination_configs = [aws_cloudwatch_log_group.waf_log_group.arn] + resource_arn = aws_wafv2_web_acl.waf_web_acl.arn + provider = aws.us-east-1 +} From e5f2921361a1415d2def27698419977fee47a464 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 27 Jan 2026 09:29:51 +0000 Subject: [PATCH 028/181] Adding the kms module --- infrastructure/modules/kms/main.tf | 46 +++++++++++++++++++++++++ infrastructure/modules/kms/outputs.tf | 7 ++++ infrastructure/modules/kms/variables.tf | 40 +++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 infrastructure/modules/kms/main.tf create mode 100644 infrastructure/modules/kms/outputs.tf create mode 100644 infrastructure/modules/kms/variables.tf diff --git a/infrastructure/modules/kms/main.tf b/infrastructure/modules/kms/main.tf new file mode 100644 index 00000000..8e42220e --- /dev/null +++ b/infrastructure/modules/kms/main.tf @@ -0,0 +1,46 @@ +resource "aws_kms_key" "encryption_key" { + description = var.description + enable_key_rotation = var.enable_key_rotation + rotation_period_in_days = var.kms_rotation_period_in_days + policy = jsonencode({ + Version = "2012-10-17" + Statement = concat([ + { + "Sid" : "SetAccountRootPermissions", + "Effect" : "Allow", + "Principal" : { + "AWS" : "arn:aws:iam::${var.account_id}:root" + }, + "Action" : "kms:*", + "Resource" : "*" + }, + { + "Sid" : "AllowInAccountUseOfKmsKey", + "Effect" : "Allow", + "Principal" : { + "Service" : "${var.aws_service_name}" + }, + "Action" : [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:DescribeKey", + "kms:ListAliases", + "kms:ListKeys" + ], + "Resource" : "*", + "Condition" : { + "StringEquals" : { + "aws:SourceAccount" : var.account_id + } + } + } + ], var.additional_policy_statements) + }) +} + +resource "aws_kms_alias" "encryption_key_alias" { + name = var.alias_name + target_key_id = aws_kms_key.encryption_key.key_id +} diff --git a/infrastructure/modules/kms/outputs.tf b/infrastructure/modules/kms/outputs.tf new file mode 100644 index 00000000..e1ea9733 --- /dev/null +++ b/infrastructure/modules/kms/outputs.tf @@ -0,0 +1,7 @@ +output "key_id" { + value = aws_kms_key.encryption_key.key_id +} + +output "arn" { + value = aws_kms_key.encryption_key.arn +} diff --git a/infrastructure/modules/kms/variables.tf b/infrastructure/modules/kms/variables.tf new file mode 100644 index 00000000..b49fbff7 --- /dev/null +++ b/infrastructure/modules/kms/variables.tf @@ -0,0 +1,40 @@ +variable "description" { + description = "The description for the KMS key." + type = string + default = "KMS key managed by Terraform" +} + +variable "kms_rotation_period_in_days" { + description = "The number of days in the rotation period for the KMS key." + type = number + default = 365 +} + +variable "alias_name" { + description = "The alias name for the KMS key." + type = string + default = "" +} + +variable "account_id" { + description = "The AWS account ID where the KMS key will be created." + type = string + default = "" +} + +variable "aws_service_name" { + description = "The AWS service name that will be allowed to use the KMS key." + type = string +} + +variable "enable_key_rotation" { + description = "Whether to enable key rotation for the KMS key." + type = bool + default = true +} + +variable "additional_policy_statements" { + type = list(any) + default = [] + description = "Additional statements to add to the KMS key policy" +} From e9ac333a12a1e7908defe51093e79bb9e1c65760 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 27 Jan 2026 09:43:16 +0000 Subject: [PATCH 029/181] Adding the kms to the s3 bucket --- infrastructure/modules/s3/main.tf | 32 ++++++++++++++++++++++++-- infrastructure/modules/s3/variables.tf | 12 ++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/infrastructure/modules/s3/main.tf b/infrastructure/modules/s3/main.tf index e5c27706..1139f24a 100755 --- a/infrastructure/modules/s3/main.tf +++ b/infrastructure/modules/s3/main.tf @@ -29,13 +29,20 @@ module "s3" { ignore_public_acls = true restrict_public_buckets = true - server_side_encryption_configuration = { + server_side_encryption_configuration = var.enable_kms_encryption ? { + rule = { + apply_server_side_encryption_by_default = { + sse_algorithm = "aws:kms" + kms_master_key_id = var.s3_encryption_key_arn # gitleaks:allow + } + } + } : { rule = { apply_server_side_encryption_by_default = { sse_algorithm = "AES256" } } - } + } logging = var.s3_logging_bucket != "" ? { target_bucket = var.s3_logging_bucket @@ -48,3 +55,24 @@ module "s3" { website = var.website_map } + +resource "aws_s3_bucket_policy" "enforce_kms_truststore" { + count = var.enable_kms_encryption ? 1 : 0 + bucket = module.s3.s3_bucket_id + policy = jsonencode({ Version = "2012-10-17" + Statement = [ + { + Sid = "DenyUnencryptedUploads" + Effect = "Deny" + Principal = "*" + Action = "s3:PutObject" + Resource = "${module.s3.s3_bucket_arn}/*", + Condition = { + StringNotEquals = { + "s3:x-amz-server-side-encryption" : var.s3_encryption_key_arn + } + } + } + ] + }) +} diff --git a/infrastructure/modules/s3/variables.tf b/infrastructure/modules/s3/variables.tf index 8c9e65c9..a5fa0f58 100755 --- a/infrastructure/modules/s3/variables.tf +++ b/infrastructure/modules/s3/variables.tf @@ -70,3 +70,15 @@ variable "s3_logging_bucket" { type = string default = "" } + +variable "s3_encryption_key_arn" { + description = "The ARN of the KMS key to use for server-side encryption if required" + type = string + default = null +} + +variable "enable_kms_encryption" { + description = "Whether to enable server-side KMS encryption for the S3 bucket" + type = bool + default = false +} From 4d1a807d1e0fb1dae6e07eaf45ac3b42653d4f33 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 27 Jan 2026 16:04:30 +0000 Subject: [PATCH 030/181] adding fixes to the account wide stack --- infrastructure/.gitignore | 1 + infrastructure/account_wide.tfvars | 31 +++ .../environments/dev/account_wide.tfvars | 20 ++ .../stacks/account_wide/cloudwatch.tf | 72 ------- infrastructure/stacks/account_wide/data.tf | 28 --- .../stacks/account_wide/ec2_performance.tf | 147 ------------- infrastructure/stacks/account_wide/kms.tf | 111 ---------- .../account_wide/opensearch_collection.tf | 12 -- infrastructure/stacks/account_wide/s3.tf | 15 -- infrastructure/stacks/account_wide/secrets.tf | 56 ----- .../stacks/account_wide/security_group.tf | 89 -------- .../stacks/account_wide/ssm_parameters.tf | 59 ----- infrastructure/stacks/account_wide/vpce.tf | 12 -- infrastructure/stacks/account_wide/vpn.tf | 100 --------- infrastructure/stacks/account_wide/waf.tf | 203 ------------------ 15 files changed, 52 insertions(+), 904 deletions(-) create mode 100644 infrastructure/account_wide.tfvars create mode 100644 infrastructure/environments/dev/account_wide.tfvars delete mode 100644 infrastructure/stacks/account_wide/cloudwatch.tf delete mode 100644 infrastructure/stacks/account_wide/ec2_performance.tf delete mode 100644 infrastructure/stacks/account_wide/opensearch_collection.tf delete mode 100644 infrastructure/stacks/account_wide/secrets.tf delete mode 100644 infrastructure/stacks/account_wide/security_group.tf delete mode 100644 infrastructure/stacks/account_wide/ssm_parameters.tf delete mode 100644 infrastructure/stacks/account_wide/vpn.tf delete mode 100644 infrastructure/stacks/account_wide/waf.tf diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index 6de44923..ac09308b 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -24,6 +24,7 @@ crash.*.log !github_runner.tfvars !account_security.tfvars !terraform_management.tfvars +!account_wide.tfvars # Ignore override files as they are usually used to override resources locally and so # are not checked in diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars new file mode 100644 index 00000000..a59b2b35 --- /dev/null +++ b/infrastructure/account_wide.tfvars @@ -0,0 +1,31 @@ +create_database_subnet_group = true +create_database_route_table = true +create_database_internet_gateway_route = false +create_database_nat_gateway_route = true +database_dedicated_network_acl = true +private_dedicated_network_acl = true +public_dedicated_network_acl = true + +opensearch_type = "SEARCH" +opensearch_standby_replicas = "DISABLED" +opensearch_create_access_policy = false +opensearch_create_network_policy = false +opensearch_collection_name = "-osc" + +waf_log_group_policy_name = "waf-log-group-policy" +osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" + +vpc_flow_logs_bucket_name = "vpc-flow-logs" +subnet_flow_logs_bucket_name = "subnet-flow-logs" +flow_log_destination_type = "s3" +flow_log_file_format = "parquet" +flow_log_s3_versioning = false +flow_logs_s3_expiration_days = 10 + +# WAF +waf_name = "frontend-waf-web-acl" +waf_scope = "CLOUDFRONT" +waf_log_group = "web-acl-logs" +waf_log_group_class = "STANDARD" + +enable_s3_kms_encryption = true diff --git a/infrastructure/environments/dev/account_wide.tfvars b/infrastructure/environments/dev/account_wide.tfvars new file mode 100644 index 00000000..3d1ac664 --- /dev/null +++ b/infrastructure/environments/dev/account_wide.tfvars @@ -0,0 +1,20 @@ +vpc = { + name = "vpc" + cidr = "10.170.0.0/16" + + public_subnet_a = "10.170.1.0/21" + public_subnet_b = "10.170.2.0/21" + public_subnet_c = "10.170.3.0/21" + + private_subnet_a = "10.170.131.0/21" + private_subnet_b = "10.170.132.0/21" + private_subnet_c = "10.170.133.0/21" +} + +enable_flow_log = false +flow_log_s3_force_destroy = true + +# Single NAT Gateway +enable_nat_gateway = true +single_nat_gateway = true +one_nat_gateway_per_az = false diff --git a/infrastructure/stacks/account_wide/cloudwatch.tf b/infrastructure/stacks/account_wide/cloudwatch.tf deleted file mode 100644 index 0f9c3a61..00000000 --- a/infrastructure/stacks/account_wide/cloudwatch.tf +++ /dev/null @@ -1,72 +0,0 @@ -# AWS WAF CloudWatch Log Group Resource Policy -resource "aws_cloudwatch_log_resource_policy" "waf_log_group_policy" { - policy_document = data.aws_iam_policy_document.waf_log_group_policy_document.json - policy_name = "${local.resource_prefix}-${var.waf_log_group_policy_name}" - provider = aws.us-east-1 -} - -data "aws_iam_policy_document" "waf_log_group_policy_document" { - version = "2012-10-17" - statement { - effect = "Allow" - principals { - identifiers = ["delivery.logs.amazonaws.com"] - type = "Service" - } - actions = ["logs:CreateLogStream", "logs:PutLogEvents"] - resources = [ - "arn:aws:logs:${var.aws_region_us_east_1}:${data.aws_caller_identity.current.account_id}:log-group:aws-waf-logs-ftrs-dos-${var.environment}*:log-stream:*" - ] - condition { - test = "ArnLike" - values = ["arn:aws:logs:${var.aws_region_us_east_1}:${data.aws_caller_identity.current.account_id}:*"] - variable = "aws:SourceArn" - } - condition { - test = "StringEquals" - values = [tostring(data.aws_caller_identity.current.account_id)] - variable = "aws:SourceAccount" - } - } - provider = aws.us-east-1 -} - -# AWS OSIS & API Gateway CloudWatch Log Group Resource Policy -resource "aws_cloudwatch_log_resource_policy" "osis_apigw_log_group_policy" { - policy_document = data.aws_iam_policy_document.osis_apigw_log_group_policy_document.json - policy_name = "${local.resource_prefix}-${var.osis_apigw_log_group_policy_name}" -} - -data "aws_iam_policy_document" "osis_apigw_log_group_policy_document" { - version = "2012-10-17" - statement { - effect = "Allow" - principals { - identifiers = ["delivery.logs.amazonaws.com"] - type = "Service" - } - actions = ["logs:CreateLogStream", "logs:PutLogEvents"] - resources = [ - "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/vendedlogs/OpenSearchIngestion/dynamodb-to-os-${var.environment}*:log-stream:*", - "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/apigateway/ftrs-dos-${var.environment}*/default:log-stream:*" - ] - condition { - test = "ArnLike" - values = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"] - variable = "aws:SourceArn" - } - condition { - test = "StringEquals" - values = [tostring(data.aws_caller_identity.current.account_id)] - variable = "aws:SourceAccount" - } - } -} - -resource "aws_cloudwatch_log_group" "waf_log_group" { - # checkov:skip=CKV_AWS_158: Justification: Using AWS default encryption. - name = "${var.waf_log_group_name_prefix}${local.resource_prefix}-${var.waf_log_group}" - retention_in_days = var.waf_log_group_retention_days - log_group_class = var.waf_log_group_class - provider = aws.us-east-1 -} diff --git a/infrastructure/stacks/account_wide/data.tf b/infrastructure/stacks/account_wide/data.tf index f3f85d35..c6dc158e 100644 --- a/infrastructure/stacks/account_wide/data.tf +++ b/infrastructure/stacks/account_wide/data.tf @@ -1,12 +1,3 @@ -data "aws_acm_certificate" "vpn_cert" { - count = var.environment == "dev" ? 1 : 0 - - domain = "${local.account_prefix}-vpn" - types = ["IMPORTED"] - statuses = ["ISSUED"] - most_recent = true -} - data "aws_availability_zones" "available_azs" { state = "available" filter { @@ -14,25 +5,6 @@ data "aws_availability_zones" "available_azs" { values = ["opt-in-not-required"] } } - -data "aws_ami" "al2023" { - most_recent = true - owners = ["amazon"] - - filter { - name = "name" - values = var.performance_ami_name_pattern - } - filter { - name = "architecture" - values = var.performance_ami_architectures - } - filter { - name = "root-device-type" - values = ["ebs"] - } -} - data "aws_subnet" "vpc_private_subnets_by_count" { count = length(module.vpc.private_subnets) id = module.vpc.private_subnets[count.index] diff --git a/infrastructure/stacks/account_wide/ec2_performance.tf b/infrastructure/stacks/account_wide/ec2_performance.tf deleted file mode 100644 index 0fc42811..00000000 --- a/infrastructure/stacks/account_wide/ec2_performance.tf +++ /dev/null @@ -1,147 +0,0 @@ -// Performance EC2 instance(s) for performance testing in the account-wide stack. -// Creates a small Amazon Linux 2023 instance in a private subnet, reachable via SSM (Session Manager) only. -// Installs Apache JMeter on first boot and powers off the instance when installation completes (configurable). - - -locals { - # Name tag for Performance EC2 instance, scoped to this stack - performance_name = "${local.account_prefix}-performance" -} - -resource "aws_instance" "performance" { - ami = data.aws_ami.al2023.id - instance_type = var.performance_instance_type - subnet_id = element(module.vpc.private_subnets, 0) - vpc_security_group_ids = [aws_security_group.performance_ec2_sg.id] - iam_instance_profile = aws_iam_instance_profile.ec2_performance_instance_profile.name - associate_public_ip_address = false - ebs_optimized = true - monitoring = true - - user_data = templatefile("${path.module}/templates/performance_user_data.sh.tmpl", { - aws_region = var.aws_region, - performance_version = var.performance_version, - performance_poweroff_after_setup = var.performance_poweroff_after_setup, - performance_jwt_dependency_version = var.performance_jwt_dependency_version - }) - user_data_replace_on_change = true - - root_block_device { - encrypted = true - volume_size = var.performance_volume_size - volume_type = "gp3" - } - - metadata_options { - http_tokens = "required" - http_put_response_hop_limit = 1 - } - - instance_initiated_shutdown_behavior = "stop" - - tags = { - Name = local.performance_name - Role = "performance" - } - - - depends_on = [ - aws_iam_role_policy_attachment.ec2_performance_ssm_core - ] -} - -# IAM role and instance profile for Performance EC2 -resource "aws_iam_role" "ec2_performance_role" { - name = "${local.account_prefix}-ec2-performance" - assume_role_policy = jsonencode({ - Version = "2012-10-17", - Statement = [{ - Action = "sts:AssumeRole", - Effect = "Allow", - Principal = { Service = "ec2.amazonaws.com" } - }] - }) -} - -resource "aws_iam_role_policy_attachment" "ec2_performance_ssm_core" { - role = aws_iam_role.ec2_performance_role.name - policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" -} - -resource "aws_iam_instance_profile" "ec2_performance_instance_profile" { - name = "${local.account_prefix}-instance-profile-performance" - role = aws_iam_role.ec2_performance_role.name -} - -# S3 access required for Performance EC2 (explicit performance buckets only) -data "aws_iam_policy_document" "ec2_performance_s3" { - statement { - sid = "AllowS3BucketMetadataForPerformance" - actions = [ - "s3:ListBucket", - "s3:GetBucketLocation", - "s3:ListBucketMultipartUploads" - ] - resources = [ - module.performance_s3.s3_bucket_arn - ] - } - - statement { - sid = "AllowS3ObjectAccessForPerformance" - actions = [ - "s3:GetObject", - "s3:GetObjectVersion", - "s3:PutObject", - "s3:DeleteObject", - "s3:CreateMultipartUpload", - "s3:UploadPart", - "s3:ListMultipartUploadParts", - "s3:CompleteMultipartUpload", - "s3:AbortMultipartUpload" - ] - resources = [ - "${module.performance_s3.s3_bucket_arn}/*" - ] - } -} - -resource "aws_iam_role_policy" "ec2_performance_s3" { - name = "${local.account_prefix}-ec2-performance-s3" - role = aws_iam_role.ec2_performance_role.id - policy = data.aws_iam_policy_document.ec2_performance_s3.json -} - -# Secrets Manager read access restricted to explicit secrets used by performance tests. To be expanded -data "aws_iam_policy_document" "ec2_performance_secrets" { - statement { - sid = "AllowGetExplicitPerformanceSecrets" - actions = [ - "secretsmanager:GetSecretValue", - "secretsmanager:DescribeSecret" - ] - resources = [ - aws_secretsmanager_secret.api_jmeter_pks_key[0].arn, - aws_secretsmanager_secret.api_ca_cert_secret[0].arn, - aws_secretsmanager_secret.api_ca_pk_secret[0].arn - ] - } - - statement { - effect = "Allow" - actions = [ - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ] - resources = [ - module.secrets_manager_encryption_key.arn - ] - } -} - -resource "aws_iam_role_policy" "ec2_performance_secrets" { - name = "${local.account_prefix}-ec2-performance-secrets" - role = aws_iam_role.ec2_performance_role.id - policy = data.aws_iam_policy_document.ec2_performance_secrets.json -} diff --git a/infrastructure/stacks/account_wide/kms.tf b/infrastructure/stacks/account_wide/kms.tf index 47e78cf9..816a9c69 100644 --- a/infrastructure/stacks/account_wide/kms.tf +++ b/infrastructure/stacks/account_wide/kms.tf @@ -1,70 +1,3 @@ -module "sqs_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.sqs - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "sqs.amazonaws.com" - description = "Encryption key for SQS queues in ${var.environment} environment" -} - -module "secrets_manager_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.secrets_manager - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "secretsmanager.amazonaws.com" - description = "Encryption key for Secrets Manager in ${var.environment} environment" - - additional_policy_statements = [ - { - Sid = "AllowEC2SecretsAccess" - Effect = "Allow" - Principal = { - AWS = [ - aws_iam_role.ec2_performance_role.arn - ] - } - Action = [ - "kms:Decrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ] - Resource = "*" - }, - { - Sid = "AllowGitHubRunnerAccess" - Effect = "Allow" - Principal = { - AWS = [ - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.app_github_runner_role_name}", - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.account_github_runner_role_name}" - ] - } - Action = [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ] - Resource = "*" - } - ] -} - -module "opensearch_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.opensearch - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "opensearchservice.amazonaws.com" - description = "Encryption key for OpenSearch in ${var.environment} environment" -} - -module "rds_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.rds - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "rds.amazonaws.com" - description = "Encryption key for RDS instances in ${var.environment} environment" -} - module "dynamodb_encryption_key" { source = "../../modules/kms" alias_name = local.kms_aliases.dynamodb @@ -72,47 +5,3 @@ module "dynamodb_encryption_key" { aws_service_name = "dynamodb.amazonaws.com" description = "Encryption key for DynamoDB tables in ${var.environment} environment" } - -module "dms_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.dms - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "dms.amazonaws.com" - description = "Encryption key for DMS in ${var.environment} environment" -} - -module "ssm_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.ssm - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "ssm.amazonaws.com" - description = "Encryption key for SSM parameters in ${var.environment} environment" - - additional_policy_statements = [ - { - Sid = "AllowGitHubRunnerAccess" - Effect = "Allow" - Principal = { - AWS = [ - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.app_github_runner_role_name}", - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${local.account_prefix}-${var.account_github_runner_role_name}" - ] - } - Action = [ - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey*", - "kms:DescribeKey" - ] - Resource = "*" - } - ] -} - -module "s3_encryption_key" { - source = "../../modules/kms" - alias_name = local.kms_aliases.s3 - account_id = data.aws_caller_identity.current.account_id - aws_service_name = "s3.amazonaws.com" - description = "Encryption key for S3 buckets in ${var.environment} environment" -} diff --git a/infrastructure/stacks/account_wide/opensearch_collection.tf b/infrastructure/stacks/account_wide/opensearch_collection.tf deleted file mode 100644 index b8acd39d..00000000 --- a/infrastructure/stacks/account_wide/opensearch_collection.tf +++ /dev/null @@ -1,12 +0,0 @@ -module "opensearch_serverless" { - # Module version: 1.7.0 - source = "git::https://github.com/terraform-aws-modules/terraform-aws-opensearch.git//modules/collection?ref=6f7113edebb53779de7225634f7e914bc6d59c8c" - - name = "${local.project_prefix}${var.opensearch_collection_name}" - description = "OpenSearch Serverless collection for DynamoDB ingestion" - type = var.opensearch_type - standby_replicas = var.opensearch_standby_replicas - - create_access_policy = var.opensearch_create_access_policy - create_network_policy = var.opensearch_create_network_policy -} diff --git a/infrastructure/stacks/account_wide/s3.tf b/infrastructure/stacks/account_wide/s3.tf index 64490569..0b9ae562 100644 --- a/infrastructure/stacks/account_wide/s3.tf +++ b/infrastructure/stacks/account_wide/s3.tf @@ -104,18 +104,3 @@ data "aws_iam_policy_document" "subnet_flow_logs_s3_bucket_policy_doc" { resources = [module.subnet_flow_logs_s3_bucket.s3_bucket_arn] } } - -module "trust_store_s3_bucket" { - # This module creates an S3 bucket for the trust store used for MTLS Certificates. - source = "../../modules/s3" - bucket_name = local.s3_trust_store_bucket_name - s3_logging_bucket = local.s3_logging_bucket - s3_encryption_key_arn = module.s3_encryption_key.arn - enable_kms_encryption = var.enable_s3_kms_encryption -} - -# IS Performance S3 Bucket -module "performance_s3" { - source = "../../modules/s3" - bucket_name = "${local.resource_prefix}-${var.performance_files_bucket_name}" -} diff --git a/infrastructure/stacks/account_wide/secrets.tf b/infrastructure/stacks/account_wide/secrets.tf deleted file mode 100644 index 76a0f8d5..00000000 --- a/infrastructure/stacks/account_wide/secrets.tf +++ /dev/null @@ -1,56 +0,0 @@ -resource "aws_secretsmanager_secret" "api_ca_cert_secret" { - # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.repo_name}/${var.environment}/api-ca-cert" - description = "Public certificate for mTLS authentication" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "api_ca_pk_secret" { - # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.repo_name}/${var.environment}/api-ca-pk" - description = "Private key for mTLS authentication" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "cis2_private_key" { - # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.project}/${var.environment}/cis2-private-key" - description = "Private key for CIS2 in ${var.environment} environment" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "cis2_public_key" { - # checkov:skip=CKV2_AWS_57: Justification: This will be rotated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.project}/${var.environment}/cis2-public-key" - description = "Public key for CIS2 in ${var.environment} environment in JWKS format" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - - -resource "aws_secretsmanager_secret" "api_jmeter_pks_key" { - # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.repo_name}/${var.environment}/api-jmeter-pks-key" - description = "Private key for jmeter mTLS authentication" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "dos_search_proxygen_jwt_credentials" { - # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.project}/${var.environment}/dos-search-proxygen-jwt-credentials" - description = "JWT credentials for DOS Search Proxygen in ${var.environment} environment" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "dos_search_jwt_credentials" { - # checkov:skip=CKV2_AWS_57: Justification: This is generated manually. - count = local.is_primary_environment ? 1 : 0 - name = "/${var.project}/${var.environment}/dos-search/jwt-credentials" - description = "JWT credentials for NHS Digital Onboarding test application in ${var.environment} environment" - kms_key_id = module.secrets_manager_encryption_key.key_id -} diff --git a/infrastructure/stacks/account_wide/security_group.tf b/infrastructure/stacks/account_wide/security_group.tf deleted file mode 100644 index 7b3f606a..00000000 --- a/infrastructure/stacks/account_wide/security_group.tf +++ /dev/null @@ -1,89 +0,0 @@ -resource "aws_security_group" "vpce_rds_security_group" { - name = "${local.account_prefix}-current-dos-rds-vpc-endpoint-sg" - description = "Security group for VPC Endpoint to RDS (DMS)" - vpc_id = module.vpc.vpc_id -} - -resource "aws_vpc_security_group_ingress_rule" "vpce_allow_all_ingress" { - security_group_id = aws_security_group.vpce_rds_security_group.id - referenced_security_group_id = aws_security_group.dms_replication_security_group.id - description = "Allow ingress from DMS replication instance" - ip_protocol = "tcp" - from_port = var.rds_port - to_port = var.rds_port -} - -# trivy:ignore:aws-vpc-no-public-egress-sgr : TODO https://nhsd-jira.digital.nhs.uk/browse/FTRS-386 -resource "aws_vpc_security_group_egress_rule" "vpce_allow_all_egress" { - description = "Allow all outbound traffic to RDS" - security_group_id = aws_security_group.vpce_rds_security_group.id - cidr_ipv4 = "0.0.0.0/0" - ip_protocol = "tcp" - from_port = var.rds_port - to_port = var.rds_port -} - -# Security group for Performance EC2 instance -resource "aws_security_group" "performance_ec2_sg" { - name = "${local.account_prefix}-performance-sg" - description = "Security group for Performance EC2 instance (SSM-managed)" - vpc_id = module.vpc.vpc_id - - tags = { - Name = "${local.resource_prefix}-performance-sg" - } -} - -# HTTPS egress for software installation, AWS APIs, and performance tests -# Note: 0.0.0.0/0 here still egresses via a NAT Gateway from private subnets; no inbound exposure. -# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required HTTPS egress to the internet for installs and AWS APIs; SG egress is least-privilege and NACLs restrict UDP -resource "aws_vpc_security_group_egress_rule" "performance_egress_https" { - security_group_id = aws_security_group.performance_ec2_sg.id - description = "Allow HTTPS egress (tcp/443) to the internet for installs, AWS APIs, and tests" - cidr_ipv4 = "0.0.0.0/0" - ip_protocol = "tcp" - from_port = 443 - to_port = 443 -} - -# DNS egress (UDP 53) to VPC resolver only (base+2 of VPC CIDR) -resource "aws_vpc_security_group_egress_rule" "performance_egress_dns_udp" { - security_group_id = aws_security_group.performance_ec2_sg.id - description = "Allow DNS egress (udp/53) to VPC resolver only" - cidr_ipv4 = format("%s/32", cidrhost(var.vpc["cidr"], 2)) - ip_protocol = "udp" - from_port = 53 - to_port = 53 -} - -# NTP egress (UDP 123) to public NTP servers when link-local IP cannot be used -# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required NTP/UDP 123 egress because link-local IPs cannot be referenced in this environment; NACLs restrict inbound UDP ephemeral responses -resource "aws_vpc_security_group_egress_rule" "performance_egress_ntp_udp" { - security_group_id = aws_security_group.performance_ec2_sg.id - description = "Allow NTP egress (udp/123) to public NTP servers" - cidr_ipv4 = "0.0.0.0/0" - ip_protocol = "udp" - from_port = 123 - to_port = 123 -} - - -# HTTP egress for software installation, AWS APIs, and performance tests -# Note: 0.0.0.0/0 here still egresses via a NAT Gateway from private subnets; no inbound exposure. -# trivy:ignore:aws-vpc-no-public-egress-sgr : FDOS-511 Required HTTPS egress to the internet for installs and AWS APIs; SG egress is least-privilege and NACLs restrict UDP -resource "aws_vpc_security_group_egress_rule" "performance_egress_http" { - security_group_id = aws_security_group.performance_ec2_sg.id - description = "Allow HTTP egress (tcp/80) to the internet for installs" - cidr_ipv4 = "0.0.0.0/0" - ip_protocol = "tcp" - from_port = 80 - to_port = 80 -} - -resource "aws_security_group" "dms_replication_security_group" { - # checkov:skip=CKV2_AWS_5:Works locally in the checkov checks. Also is used in the file above - name = "${local.resource_prefix}-etl-replication-sg" - description = "Security group for DMS ETL replication instance" - vpc_id = module.vpc.vpc_id - depends_on = [module.vpc] -} diff --git a/infrastructure/stacks/account_wide/ssm_parameters.tf b/infrastructure/stacks/account_wide/ssm_parameters.tf deleted file mode 100644 index b50bb95d..00000000 --- a/infrastructure/stacks/account_wide/ssm_parameters.tf +++ /dev/null @@ -1,59 +0,0 @@ -resource "aws_ssm_parameter" "dos_aws_account_id_mgmt" { - name = "/dos/${var.environment}/aws_account_id_mgmt" - description = "Id of the management account" - type = "SecureString" - tier = "Standard" - value = "default" - key_id = module.ssm_encryption_key.arn - - lifecycle { - ignore_changes = [ - value - ] - } -} - -resource "aws_ssm_parameter" "texas_vpc_endpoint_service_name" { - name = "/${local.resource_prefix}/texas-vpc-endpoint-service-name" - description = "The VPC Endpoint Service Name for the Texas RDS instance" - type = "SecureString" - tier = "Standard" - value = "changeme" # Placeholder, to be manually updated in AWS Console or via CLI later - key_id = module.ssm_encryption_key.arn - - lifecycle { - ignore_changes = [ - value - ] - } -} - -resource "aws_ssm_parameter" "cis2_client_config" { - name = "/${var.project}/${var.environment}/cis2-client-config" - description = "The CIS2 Client Configuration" - type = "SecureString" - tier = "Standard" - value = "CHANGE_ME" # Placeholder, to be manually updated in AWS Console or via CLI later - key_id = module.ssm_encryption_key.arn - - lifecycle { - ignore_changes = [ - value - ] - } -} - -resource "aws_ssm_parameter" "cis2_connection_manager" { - name = "/${var.project}/${var.environment}/cis2-connection-manager" - description = "The CIS2 Connection Manager Configuration" - type = "SecureString" - tier = "Standard" - value = "CHANGE_ME" # Placeholder, to be manually updated in AWS Console or via CLI later - key_id = module.ssm_encryption_key.arn - - lifecycle { - ignore_changes = [ - value - ] - } -} diff --git a/infrastructure/stacks/account_wide/vpce.tf b/infrastructure/stacks/account_wide/vpce.tf index 71a757cd..a6d899c0 100644 --- a/infrastructure/stacks/account_wide/vpce.tf +++ b/infrastructure/stacks/account_wide/vpce.tf @@ -48,15 +48,3 @@ resource "aws_vpc_endpoint" "s3_vpce" { Name = "${local.resource_prefix}-s3-gateway-vpc-endpoint" } } - -resource "aws_vpc_endpoint" "current_dos_rds_endpoint" { - vpc_id = module.vpc.vpc_id - service_name = aws_ssm_parameter.texas_vpc_endpoint_service_name.value - vpc_endpoint_type = "Interface" - subnet_ids = [module.vpc.private_subnets[0], module.vpc.private_subnets[1], module.vpc.private_subnets[2]] - security_group_ids = [aws_security_group.vpce_rds_security_group.id] - - tags = { - Name = "${local.resource_prefix}-current-dos-rds-vpc-endpoint" - } -} diff --git a/infrastructure/stacks/account_wide/vpn.tf b/infrastructure/stacks/account_wide/vpn.tf deleted file mode 100644 index b2485069..00000000 --- a/infrastructure/stacks/account_wide/vpn.tf +++ /dev/null @@ -1,100 +0,0 @@ -resource "aws_ec2_client_vpn_endpoint" "vpn" { - count = var.environment == "dev" ? 1 : 0 - - description = "${local.account_prefix}-vpn" - vpc_id = module.vpc.vpc_id - client_cidr_block = var.vpc["vpn_subnet"] - server_certificate_arn = data.aws_acm_certificate.vpn_cert[0].arn - - security_group_ids = [aws_security_group.vpn_security_group[0].id] - - transport_protocol = "tcp" - split_tunnel = true - - authentication_options { - type = "certificate-authentication" - root_certificate_chain_arn = data.aws_acm_certificate.vpn_cert[0].arn - } - - connection_log_options { - enabled = true - cloudwatch_log_group = aws_cloudwatch_log_group.vpn_log_group[0].name - cloudwatch_log_stream = aws_cloudwatch_log_stream.vpn_log_stream[0].name - } -} - -resource "aws_ec2_client_vpn_network_association" "database_association" { - count = var.environment == "dev" ? length(module.vpc.database_subnets) : 0 - - client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn[0].id - subnet_id = module.vpc.database_subnets[count.index] -} - -resource "aws_ec2_client_vpn_authorization_rule" "vpn_authorization_rule" { - for_each = var.environment == "dev" ? { for s in module.vpc.database_subnet_objects : s.id => s } : {} - - client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn[0].id - target_network_cidr = each.value.cidr_block - authorize_all_groups = true -} - -resource "aws_security_group" "vpn_security_group" { - count = var.environment == "dev" ? 1 : 0 - name = "${local.account_prefix}-vpn-sg" - description = "Security Group for Client VPN Endpoint" - vpc_id = module.vpc.vpc_id - - ingress { - protocol = "tcp" - from_port = 443 - to_port = 443 - cidr_blocks = ["0.0.0.0/0"] - } - - egress { - protocol = "tcp" - from_port = 443 - to_port = 443 - cidr_blocks = module.vpc.private_subnets_cidr_blocks - } - - egress { - protocol = "tcp" - from_port = 80 - to_port = 80 - cidr_blocks = module.vpc.private_subnets_cidr_blocks - } - - egress { - protocol = "tcp" - from_port = 5432 - to_port = 5432 - cidr_blocks = module.vpc.database_subnets_cidr_blocks - } -} - -resource "aws_cloudwatch_log_group" "vpn_log_group" { - count = var.environment == "dev" ? 1 : 0 - name = "${local.account_prefix}-vpn-log-group" - retention_in_days = var.log_group_retention_in_days -} - -resource "aws_cloudwatch_log_stream" "vpn_log_stream" { - count = var.environment == "dev" ? 1 : 0 - name = "${local.account_prefix}-vpn-log-stream" - log_group_name = aws_cloudwatch_log_group.vpn_log_group[0].name -} - -resource "aws_secretsmanager_secret" "vpn_ca_cert_secret" { - # checkov:skip=CKV2_AWS_57: Justification: This is generated manually for dev environment only. - count = var.environment == "dev" ? 1 : 0 - name = "/${var.repo_name}/${var.environment}/vpn-ca-cert" - kms_key_id = module.secrets_manager_encryption_key.key_id -} - -resource "aws_secretsmanager_secret" "vpn_ca_pk_secret" { - # checkov:skip=CKV2_AWS_57: Justification: This is generated manually for dev environment only. - count = var.environment == "dev" ? 1 : 0 - name = "/${var.repo_name}/${var.environment}/vpn-ca-pk" - kms_key_id = module.secrets_manager_encryption_key.key_id -} diff --git a/infrastructure/stacks/account_wide/waf.tf b/infrastructure/stacks/account_wide/waf.tf deleted file mode 100644 index 515fe409..00000000 --- a/infrastructure/stacks/account_wide/waf.tf +++ /dev/null @@ -1,203 +0,0 @@ -resource "aws_wafv2_web_acl" "waf_web_acl" { - name = "${local.resource_prefix}-${var.waf_name}" - description = "WAF Web ACL" - scope = var.waf_scope - - default_action { - allow {} - } - - rule { - name = "allowed-countries-rule" - priority = 11 - action { - block {} - } - statement { - not_statement { - statement { - geo_match_statement { - country_codes = ["GB", "JE", "IM"] - } - } - } - } - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "allowed-countries-rule" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesAmazonIpReputationList" - priority = 21 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesAmazonIpReputationList" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesAmazonIpReputationList" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesAnonymousIpList" - priority = 31 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesAnonymousIpList" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesAnonymousIpList" - sampled_requests_enabled = true - } - } - - rule { - name = "rate-limit-rule" - priority = 41 - - action { - block {} - } - - statement { - rate_based_statement { - limit = 100 - aggregate_key_type = "FORWARDED_IP" - evaluation_window_sec = 120 - forwarded_ip_config { - fallback_behavior = "NO_MATCH" - header_name = "X-Forwarded-For" - } - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "rate-limit-rule" - sampled_requests_enabled = true - } - } - rule { - name = "AWSManagedRulesCommonRuleSet" - priority = 51 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesCommonRuleSet" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesCommonRuleSet" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesKnownBadInputsRuleSet" - priority = 61 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesKnownBadInputsRuleSet" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesKnownBadInputsRuleSet" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesAdminProtectionRuleSet" - priority = 71 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesAdminProtectionRuleSet" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesAdminProtectionRuleSet" - sampled_requests_enabled = true - } - } - - rule { - name = "AWSManagedRulesBotControlRuleSet" - priority = 81 - - override_action { - none {} - } - - statement { - managed_rule_group_statement { - name = "AWSManagedRulesBotControlRuleSet" - vendor_name = "AWS" - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "AWSManagedRulesBotControlRuleSet" - sampled_requests_enabled = true - } - } - - visibility_config { - cloudwatch_metrics_enabled = true - metric_name = "${local.resource_prefix}-metric" - sampled_requests_enabled = true - } - - provider = aws.us-east-1 -} - -resource "aws_wafv2_web_acl_logging_configuration" "waf_logging_configuration" { - log_destination_configs = [aws_cloudwatch_log_group.waf_log_group.arn] - resource_arn = aws_wafv2_web_acl.waf_web_acl.arn - provider = aws.us-east-1 -} From 485671b46aa0fb5e4aae1b57541f8bd228a7a294 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 27 Jan 2026 16:09:29 +0000 Subject: [PATCH 031/181] adding fixes to the account wide stack s3 specifically --- infrastructure/modules/s3/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/s3/main.tf b/infrastructure/modules/s3/main.tf index 1139f24a..acada01c 100755 --- a/infrastructure/modules/s3/main.tf +++ b/infrastructure/modules/s3/main.tf @@ -29,7 +29,7 @@ module "s3" { ignore_public_acls = true restrict_public_buckets = true - server_side_encryption_configuration = var.enable_kms_encryption ? { + server_side_encryption_configuration = var.enable_kms_encryption ? { rule = { apply_server_side_encryption_by_default = { sse_algorithm = "aws:kms" From 612aae7646270d9288f5c6c764760471fd90e0ef Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:02:26 +0000 Subject: [PATCH 032/181] adding fixes to the account wide stack cidr block ranges specifically --- .../environments/dev/account_wide.tfvars | 12 +- .../stacks/account_wide/variables.tf | 121 ------------------ 2 files changed, 6 insertions(+), 127 deletions(-) diff --git a/infrastructure/environments/dev/account_wide.tfvars b/infrastructure/environments/dev/account_wide.tfvars index 3d1ac664..fe5e6261 100644 --- a/infrastructure/environments/dev/account_wide.tfvars +++ b/infrastructure/environments/dev/account_wide.tfvars @@ -2,13 +2,13 @@ vpc = { name = "vpc" cidr = "10.170.0.0/16" - public_subnet_a = "10.170.1.0/21" - public_subnet_b = "10.170.2.0/21" - public_subnet_c = "10.170.3.0/21" + public_subnet_a = "10.171.0.0/21" + public_subnet_b = "10.172.0.0/21" + public_subnet_c = "10.173.0.0/21" - private_subnet_a = "10.170.131.0/21" - private_subnet_b = "10.170.132.0/21" - private_subnet_c = "10.170.133.0/21" + private_subnet_a = "10.271.0.0/21" + private_subnet_b = "10.272.0.0/21" + private_subnet_c = "10.273.0.0/21" } enable_flow_log = false diff --git a/infrastructure/stacks/account_wide/variables.tf b/infrastructure/stacks/account_wide/variables.tf index 51ac0589..4f44ac37 100644 --- a/infrastructure/stacks/account_wide/variables.tf +++ b/infrastructure/stacks/account_wide/variables.tf @@ -38,41 +38,6 @@ variable "log_group_retention_in_days" { default = 7 } -variable "opensearch_type" { - description = "The type of OpenSearch" - type = string -} - -variable "opensearch_standby_replicas" { - description = "Number of standby replicas for OpenSearch" - type = string -} - -variable "opensearch_create_access_policy" { - description = "Flag to create access policy for OpenSearch" - type = bool -} - -variable "opensearch_create_network_policy" { - description = "Flag to create network policy for OpenSearch" - type = bool -} - -variable "opensearch_collection_name" { - description = "The OpenSearch Collection name" - type = string -} - -variable "waf_log_group_policy_name" { - description = "The WAF log group policy name" - type = string -} - -variable "osis_apigw_log_group_policy_name" { - description = "The OSIS & API Gateway log group policy name" - type = string -} - variable "gateway_vpc_endpoint_type" { description = "The VPC enpoint type" type = string @@ -141,92 +106,6 @@ variable "enable_flow_log" { type = bool default = false } - -variable "waf_name" { - description = "The Web ACL name for WAF" - type = string -} - -variable "waf_scope" { - description = "The scope for WAF" - type = string -} - -variable "waf_log_group" { - description = "Name for the WAF Web ACL log group" - type = string -} -variable "waf_log_group_class" { - description = "The log group class for WAF" - type = string -} - -variable "waf_log_group_name_prefix" { - description = "Prefix for WAF CloudWatch Log Group Name" - type = string - default = "aws-waf-logs-" -} - -variable "waf_log_group_retention_days" { - description = "The retention period for the Read only viewer Web ACL Log group" - type = number - default = 365 -} - -# Performance EC2 configuration -variable "performance_instance_type" { - description = "EC2 instance type for performance testing" - type = string - default = "c7a.xlarge" -} - -variable "performance_ami_name_pattern" { - description = "List of AMI name patterns to match for the performance EC2 (e.g., AL2023)" - type = list(string) - default = ["al2023-ami-*-x86_64"] -} - -variable "performance_ami_architectures" { - description = "List of acceptable CPU architectures for the performance EC2 AMI" - type = list(string) - default = ["x86_64"] -} - -variable "performance_volume_size" { - description = "Root EBS volume size (GiB) for the performance instance" - type = number - default = 30 - validation { - condition = var.performance_volume_size >= 30 - error_message = "performance_volume_size must be >= 30 GiB to satisfy the AMI snapshot minimum size" - } -} - -variable "performance_version" { - description = "Apache JMeter version to install" - type = string - default = "5.6.3" -} - -variable "performance_poweroff_after_setup" { - description = "Whether to power off the instance after installing JMeter" - type = bool - default = true -} - -variable "performance_jwt_dependency_version" { - description = "Version of java-jwt library to download" - type = string - default = "4.5.0" -} - -# Performance S3 bucket names -variable "performance_files_bucket_name" { - description = "S3 bucket name for performance files" - type = string - default = "is-performance-files-bucket" -} - variable "enable_s3_kms_encryption" { description = "Whether to enable KMS encryption for S3 buckets" type = bool From 4c651aa190bd127ec384d4ae5d19617d9168aaff Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:09:59 +0000 Subject: [PATCH 033/181] Fixed a botched edit of teh json --- .../account_github_runner_policy.json.tpl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl index fd77a61b..e8f4e234 100644 --- a/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl +++ b/infrastructure/stacks/github_runner/account_github_runner_policy.json.tpl @@ -131,7 +131,8 @@ "arn:aws:iam::*:instance-profile/${repo_name}-*", "arn:aws:iam::*:role/dms-vpc-role", "arn:aws:iam::*:role/${project}-*", - "arn:aws:iam::*:policy/${project}-*" + "arn:aws:iam::*:policy/${project}-*", + "arn:aws:iam::*:role/aws-service-role/shield.amazonaws.com/AWSServiceRoleForAWSShield" ] }, { @@ -277,6 +278,17 @@ "iam:AWSServiceName": "inspector2.amazonaws.com" } } + }, + { + "Sid": "AllowAccessAnalyser", + "Effect": "Allow", + "Action": "iam:CreateServiceLinkedRole", + "Resource": "arn:aws:iam::*:role/aws-service-role/access-analyzer.amazonaws.com/AWSServiceRoleForAccessAnalyzer", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "inspector2.amazonaws.com" + } + } } ] } From 43747035d9b2ae66dd0856c70dbd4ae083dc422f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:12:09 +0000 Subject: [PATCH 034/181] Removed db subnet from locals in the vpc.tf --- infrastructure/stacks/account_wide/vpc.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 94e04a23..10d045de 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -49,7 +49,6 @@ locals { public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"], var.vpc["private_subnet_d"], var.vpc["private_subnet_e"], var.vpc["private_subnet_f"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] - database_subnets = [var.vpc["database_subnet_a"], var.vpc["database_subnet_b"], var.vpc["database_subnet_c"]] vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] network_acls = { From d0c42d7618b98fa61535fc63b47f8da746953d17 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:18:56 +0000 Subject: [PATCH 035/181] Removed db subnet from locals in the vpc.tf --- infrastructure/stacks/account_wide/vpc.tf | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 10d045de..4a933d4c 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -159,16 +159,16 @@ resource "aws_flow_log" "private_subnet_flow_log_s3" { subnet_id = module.vpc.private_subnets[count.index] } -resource "aws_flow_log" "database_subnet_flow_log_s3" { - count = length(local.database_subnets) - log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn - log_destination_type = var.flow_log_destination_type - traffic_type = "REJECT" - destination_options { - per_hour_partition = true - } - subnet_id = module.vpc.database_subnets[count.index] -} +# resource "aws_flow_log" "database_subnet_flow_log_s3" { +# count = length(local.database_subnets) +# log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn +# log_destination_type = var.flow_log_destination_type +# traffic_type = "REJECT" +# destination_options { +# per_hour_partition = true +# } +# subnet_id = module.vpc.database_subnets[count.index] +# } # Add a CIDR Range tag to the private subnets for filtering resource "aws_ec2_tag" "private_subnet_tags" { From 1199fece5de33a97b201349c9b586500ff32cff3 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:25:34 +0000 Subject: [PATCH 036/181] Removed db subnet from locals in the vpc.tf --- infrastructure/stacks/account_wide/vpc.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 4a933d4c..b0e33bb5 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -19,7 +19,7 @@ module "vpc" { azs = slice(data.aws_availability_zones.available_azs.names, 0, 3) public_subnets = local.public_subnets private_subnets = local.private_subnets - database_subnets = local.database_subnets + #database_subnets = local.database_subnets # NACL configuration database_dedicated_network_acl = var.database_dedicated_network_acl From f77fd8a588122623f9cc5e8e66191dcaa74a9d9f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 09:53:15 +0000 Subject: [PATCH 037/181] Removed db subnet from locals in the vpc.tf --- infrastructure/account_wide.tfvars | 2 +- infrastructure/common.tfvars | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index a59b2b35..01503d8c 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -12,7 +12,7 @@ opensearch_create_access_policy = false opensearch_create_network_policy = false opensearch_collection_name = "-osc" -waf_log_group_policy_name = "waf-log-group-policy" +#waf_log_group_policy_name = "waf-log-group-policy" osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" vpc_flow_logs_bucket_name = "vpc-flow-logs" diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index be6bc16e..0e2f0272 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -4,7 +4,7 @@ service = "uec-saet" cost_centre = "P0675" data_type = "PCD" project_type = "Pilot" -public_facing = "no" +#public_facing = "no" service_category = "bronze" team_owner = "saet" From e9a24c53504c6930f200edb1cf77bd3feb4e389e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 10:03:24 +0000 Subject: [PATCH 038/181] Removed db subnet from locals in the vpc.tf --- infrastructure/stacks/account_wide/vpc.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index b0e33bb5..d1b4c94f 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -48,7 +48,7 @@ module "vpc" { locals { public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] - private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"], var.vpc["private_subnet_d"], var.vpc["private_subnet_e"], var.vpc["private_subnet_f"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] + private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] network_acls = { From 2df04dcd319caf049a660d06fa57e2653a044d2d Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 28 Jan 2026 10:14:22 +0000 Subject: [PATCH 039/181] Removed db subnet from locals in the vpc.tf --- infrastructure/environments/dev/account_wide.tfvars | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/environments/dev/account_wide.tfvars b/infrastructure/environments/dev/account_wide.tfvars index fe5e6261..b127fe38 100644 --- a/infrastructure/environments/dev/account_wide.tfvars +++ b/infrastructure/environments/dev/account_wide.tfvars @@ -6,9 +6,9 @@ vpc = { public_subnet_b = "10.172.0.0/21" public_subnet_c = "10.173.0.0/21" - private_subnet_a = "10.271.0.0/21" - private_subnet_b = "10.272.0.0/21" - private_subnet_c = "10.273.0.0/21" + private_subnet_a = "10.181.0.0/21" + private_subnet_b = "10.182.0.0/21" + private_subnet_c = "10.183.0.0/21" } enable_flow_log = false From 6cdd3052418ad25aa7c1f5210fec030057873323 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 09:57:10 +0000 Subject: [PATCH 040/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/account_wide.tfvars | 10 +++++----- infrastructure/common.tfvars | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index 01503d8c..81051768 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -22,10 +22,10 @@ flow_log_file_format = "parquet" flow_log_s3_versioning = false flow_logs_s3_expiration_days = 10 -# WAF -waf_name = "frontend-waf-web-acl" -waf_scope = "CLOUDFRONT" -waf_log_group = "web-acl-logs" -waf_log_group_class = "STANDARD" +# # WAF +# waf_name = "frontend-waf-web-acl" +# waf_scope = "CLOUDFRONT" +# waf_log_group = "web-acl-logs" +# waf_log_group_class = "STANDARD" enable_s3_kms_encryption = true diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index 0e2f0272..8593b5d1 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -8,7 +8,7 @@ project_type = "Pilot" service_category = "bronze" team_owner = "saet" -artefacts_bucket_name = "artefacts-bucket" +# artefacts_bucket_name = "artefacts-bucket" s3_logging_bucket_name = "s3-access-logs" rds_port = 5432 From 214f0a8ea03c5b7083136f41a8887b36f61e89f1 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 10:02:05 +0000 Subject: [PATCH 041/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/common.tfvars | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index 8593b5d1..af86f6ae 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -1,9 +1,9 @@ project = "saet" project_owner = "nhs-uec" service = "uec-saet" -cost_centre = "P0675" +# cost_centre = "P0675" data_type = "PCD" -project_type = "Pilot" +# project_type = "Pilot" #public_facing = "no" service_category = "bronze" team_owner = "saet" From 7df47a0011acbda185c231077a0bd3f2397237f8 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 10:06:59 +0000 Subject: [PATCH 042/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/account_wide.tfvars | 10 +++++----- infrastructure/common.tfvars | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index 81051768..991fdd8f 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -6,11 +6,11 @@ database_dedicated_network_acl = true private_dedicated_network_acl = true public_dedicated_network_acl = true -opensearch_type = "SEARCH" -opensearch_standby_replicas = "DISABLED" -opensearch_create_access_policy = false -opensearch_create_network_policy = false -opensearch_collection_name = "-osc" +# opensearch_type = "SEARCH" +# opensearch_standby_replicas = "DISABLED" +# opensearch_create_access_policy = false +# opensearch_create_network_policy = false +# opensearch_collection_name = "-osc" #waf_log_group_policy_name = "waf-log-group-policy" osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index af86f6ae..9b803e91 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -2,7 +2,7 @@ project = "saet" project_owner = "nhs-uec" service = "uec-saet" # cost_centre = "P0675" -data_type = "PCD" +# data_type = "PCD" # project_type = "Pilot" #public_facing = "no" service_category = "bronze" From 377c2d2b1a3e17ec725839de25368840b8fa58bd Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 10:14:21 +0000 Subject: [PATCH 043/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/common.tfvars | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index 9b803e91..b673b4b3 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -5,10 +5,10 @@ service = "uec-saet" # data_type = "PCD" # project_type = "Pilot" #public_facing = "no" -service_category = "bronze" +# service_category = "bronze" team_owner = "saet" # artefacts_bucket_name = "artefacts-bucket" s3_logging_bucket_name = "s3-access-logs" -rds_port = 5432 +# rds_port = 5432 From 6cff29a2ac6c778f274881673d253ea04f38bdcb Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 10:19:35 +0000 Subject: [PATCH 044/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/account_wide.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index 991fdd8f..32f645dd 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -13,7 +13,7 @@ public_dedicated_network_acl = true # opensearch_collection_name = "-osc" #waf_log_group_policy_name = "waf-log-group-policy" -osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" +# osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" vpc_flow_logs_bucket_name = "vpc-flow-logs" subnet_flow_logs_bucket_name = "subnet-flow-logs" From 3723ac34ee05e4114a8767afdd4e2a9a11ec542b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 10:22:48 +0000 Subject: [PATCH 045/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/common.tfvars | 2 +- infrastructure/environments/dev/environment.tfvars | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index b673b4b3..3d9d0426 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -1,6 +1,6 @@ project = "saet" project_owner = "nhs-uec" -service = "uec-saet" +# service = "uec-saet" # cost_centre = "P0675" # data_type = "PCD" # project_type = "Pilot" diff --git a/infrastructure/environments/dev/environment.tfvars b/infrastructure/environments/dev/environment.tfvars index 9b5727d1..15f55020 100644 --- a/infrastructure/environments/dev/environment.tfvars +++ b/infrastructure/environments/dev/environment.tfvars @@ -1,3 +1,3 @@ # environment specific values that are applicable to more than one stack environment = "dev" -data_classification = "3" +# data_classification = "3" From fe424ffc144a717580b018f5d56c36f87bd243f4 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 11:08:09 +0000 Subject: [PATCH 046/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/common.tfvars | 6 +++--- infrastructure/environments/dev/environment.tfvars | 2 +- infrastructure/modules/dynamodb/main.tf | 2 +- infrastructure/modules/s3/main.tf | 4 ++-- infrastructure/stacks/account_wide/vpc.tf | 12 ++++++------ 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index 3d9d0426..db2d7534 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -1,12 +1,12 @@ -project = "saet" -project_owner = "nhs-uec" +project = "saet" +project_owner = "nhs-uec" # service = "uec-saet" # cost_centre = "P0675" # data_type = "PCD" # project_type = "Pilot" #public_facing = "no" # service_category = "bronze" -team_owner = "saet" +team_owner = "saet" # artefacts_bucket_name = "artefacts-bucket" s3_logging_bucket_name = "s3-access-logs" diff --git a/infrastructure/environments/dev/environment.tfvars b/infrastructure/environments/dev/environment.tfvars index 15f55020..60d846d6 100644 --- a/infrastructure/environments/dev/environment.tfvars +++ b/infrastructure/environments/dev/environment.tfvars @@ -1,3 +1,3 @@ # environment specific values that are applicable to more than one stack -environment = "dev" +environment = "dev" # data_classification = "3" diff --git a/infrastructure/modules/dynamodb/main.tf b/infrastructure/modules/dynamodb/main.tf index 10afc85d..3fe7594f 100755 --- a/infrastructure/modules/dynamodb/main.tf +++ b/infrastructure/modules/dynamodb/main.tf @@ -21,7 +21,7 @@ module "dynamodb_table" { # Module version: 4.3.0 source = "git::https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git?ref=1ab93ca82023b72fe37de7f17cc10714867b2d4f" - name = "${var.table_name}" + name = var.table_name hash_key = var.hash_key range_key = var.range_key autoscaling_enabled = var.autoscaling_enabled diff --git a/infrastructure/modules/s3/main.tf b/infrastructure/modules/s3/main.tf index acada01c..b67b9041 100755 --- a/infrastructure/modules/s3/main.tf +++ b/infrastructure/modules/s3/main.tf @@ -18,7 +18,7 @@ module "s3" { # Module version: 5.7.0 source = "git::https://github.com/terraform-aws-modules/terraform-aws-s3-bucket.git?ref=c375418373496865e2770ad8aabfaf849d4caee5" - bucket = "${var.bucket_name}" + bucket = var.bucket_name attach_policy = var.attach_policy policy = var.policy lifecycle_rule = var.lifecycle_rule_inputs @@ -42,7 +42,7 @@ module "s3" { sse_algorithm = "AES256" } } - } + } logging = var.s3_logging_bucket != "" ? { target_bucket = var.s3_logging_bucket diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index d1b4c94f..34b73a33 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -16,9 +16,9 @@ module "vpc" { create_database_nat_gateway_route = var.create_database_nat_gateway_route database_subnet_group_name = "${local.account_prefix}-database-subnet-group" - azs = slice(data.aws_availability_zones.available_azs.names, 0, 3) - public_subnets = local.public_subnets - private_subnets = local.private_subnets + azs = slice(data.aws_availability_zones.available_azs.names, 0, 3) + public_subnets = local.public_subnets + private_subnets = local.private_subnets #database_subnets = local.database_subnets # NACL configuration @@ -47,9 +47,9 @@ module "vpc" { locals { - public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] - private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] - vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] + public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] + private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] + vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] network_acls = { From dc3462d6102ab21771515200dcfb9ed07abd46ec Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 29 Jan 2026 15:36:41 +0000 Subject: [PATCH 047/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/environments/dev/account_wide.tfvars | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/environments/dev/account_wide.tfvars b/infrastructure/environments/dev/account_wide.tfvars index b127fe38..9a13a53d 100644 --- a/infrastructure/environments/dev/account_wide.tfvars +++ b/infrastructure/environments/dev/account_wide.tfvars @@ -6,9 +6,9 @@ vpc = { public_subnet_b = "10.172.0.0/21" public_subnet_c = "10.173.0.0/21" - private_subnet_a = "10.181.0.0/21" - private_subnet_b = "10.182.0.0/21" - private_subnet_c = "10.183.0.0/21" + private_subnet_a = "10.179.0.0/21" + private_subnet_b = "10.178.0.0/21" + private_subnet_c = "10.177.0.0/21" } enable_flow_log = false From 6755bf117035919d3934426a486b5b3398950e12 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 14:14:42 +0000 Subject: [PATCH 048/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/stacks/account_wide/vpc.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 34b73a33..b133f2bd 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -49,7 +49,7 @@ locals { public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] - vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] + # vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] network_acls = { From a57ad6ebc2b8d97234830b17e863c971ac31b5de Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 14:23:49 +0000 Subject: [PATCH 049/181] Fixing formatting issues in jmx file for pipeline to run --- infrastructure/environments/dev/account_wide.tfvars | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/environments/dev/account_wide.tfvars b/infrastructure/environments/dev/account_wide.tfvars index 9a13a53d..d69d5cde 100644 --- a/infrastructure/environments/dev/account_wide.tfvars +++ b/infrastructure/environments/dev/account_wide.tfvars @@ -2,13 +2,13 @@ vpc = { name = "vpc" cidr = "10.170.0.0/16" - public_subnet_a = "10.171.0.0/21" - public_subnet_b = "10.172.0.0/21" - public_subnet_c = "10.173.0.0/21" + public_subnet_a = "10.170.0.0/21" + public_subnet_b = "10.170.8.0/21" + public_subnet_c = "10.170.16.0/21" - private_subnet_a = "10.179.0.0/21" - private_subnet_b = "10.178.0.0/21" - private_subnet_c = "10.177.0.0/21" + private_subnet_a = "10.170.24.0/21" + private_subnet_b = "10.170.32.0/21" + private_subnet_c = "10.170.40.0/21" } enable_flow_log = false From 9cb3fcaa9af89d6c1625c8dc282f7156fbfa034a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 15:35:28 +0000 Subject: [PATCH 050/181] Added extra information into the pre commit file --- infrastructure/account_wide.tfvars | 24 ++--------------- .../stacks/account_wide/variables.tf | 20 -------------- infrastructure/stacks/account_wide/vpc.tf | 3 +-- scripts/config/pre-commit.yaml | 15 +++++++++++ scripts/githooks/check-branch-name.sh | 27 +++++++++++++++++++ scripts/githooks/check-english-usage.sh | 1 - 6 files changed, 45 insertions(+), 45 deletions(-) create mode 100755 scripts/githooks/check-branch-name.sh diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index 32f645dd..fcf22227 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -1,19 +1,5 @@ -create_database_subnet_group = true -create_database_route_table = true -create_database_internet_gateway_route = false -create_database_nat_gateway_route = true -database_dedicated_network_acl = true -private_dedicated_network_acl = true -public_dedicated_network_acl = true - -# opensearch_type = "SEARCH" -# opensearch_standby_replicas = "DISABLED" -# opensearch_create_access_policy = false -# opensearch_create_network_policy = false -# opensearch_collection_name = "-osc" - -#waf_log_group_policy_name = "waf-log-group-policy" -# osis_apigw_log_group_policy_name = "osis-apigw-log-group-policy" +private_dedicated_network_acl = true +public_dedicated_network_acl = true vpc_flow_logs_bucket_name = "vpc-flow-logs" subnet_flow_logs_bucket_name = "subnet-flow-logs" @@ -22,10 +8,4 @@ flow_log_file_format = "parquet" flow_log_s3_versioning = false flow_logs_s3_expiration_days = 10 -# # WAF -# waf_name = "frontend-waf-web-acl" -# waf_scope = "CLOUDFRONT" -# waf_log_group = "web-acl-logs" -# waf_log_group_class = "STANDARD" - enable_s3_kms_encryption = true diff --git a/infrastructure/stacks/account_wide/variables.tf b/infrastructure/stacks/account_wide/variables.tf index 4f44ac37..87906e60 100644 --- a/infrastructure/stacks/account_wide/variables.tf +++ b/infrastructure/stacks/account_wide/variables.tf @@ -13,26 +13,6 @@ variable "one_nat_gateway_per_az" { type = bool } -variable "create_database_subnet_group" { - description = "Whether to create a database subnet group for RDS" - type = bool -} - -variable "create_database_route_table" { - description = "Whether to create a database route table for RDS" - type = bool -} - -variable "create_database_internet_gateway_route" { - description = "Whether to create an internet gateway route for public database access" - type = bool -} - -variable "create_database_nat_gateway_route" { - description = "Whether to create a NAT gateway route for the database" - type = bool -} - variable "log_group_retention_in_days" { description = "Number of days to retain logs" default = 7 diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index b133f2bd..26a5c95e 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -48,8 +48,7 @@ module "vpc" { locals { public_subnets = [var.vpc["public_subnet_a"], var.vpc["public_subnet_b"], var.vpc["public_subnet_c"]] - private_subnets = var.environment == "dev" ? [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] : [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] - # vpn_subnets = var.environment == "dev" ? [var.vpc["vpn_subnet"]] : [] + private_subnets = [var.vpc["private_subnet_a"], var.vpc["private_subnet_b"], var.vpc["private_subnet_c"]] network_acls = { diff --git a/scripts/config/pre-commit.yaml b/scripts/config/pre-commit.yaml index 642dcdcd..ea222de6 100644 --- a/scripts/config/pre-commit.yaml +++ b/scripts/config/pre-commit.yaml @@ -1,4 +1,11 @@ repos: +- repo: local + hooks: + - id: check-branch-name + name: Check branch name + entry: ./scripts/githooks/check-branch-name.sh + language: script + pass_filenames: false - repo: local hooks: - id: scan-secrets @@ -36,3 +43,11 @@ repos: name: Lint python entry: make test-lint language: system +- repo: local + hooks: + - id: check-english-usage + name: Check English usage + entry: ./scripts/githooks/check-english-usage.sh + args: ["check=staged-changes"] + language: script + pass_filenames: false diff --git a/scripts/githooks/check-branch-name.sh b/scripts/githooks/check-branch-name.sh new file mode 100755 index 00000000..6e1950e2 --- /dev/null +++ b/scripts/githooks/check-branch-name.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +exit_code=0 + +function check_git_branch_name { + BRANCH=$(echo "$1" | tr '[:upper:]' '[:lower:]') + VALID_FORMAT=$(check_git_branch_name_format "$BRANCH") + if [[ ! -z "$VALID_FORMAT" ]] ; then + echo "Branch name $1 does not match the naming pattern" + echo Naming pattern = task or hotfix/hyphenated JIRA ref followed by underscore or hyphen followed by max 45 alphanumerics, hyphens or underscores starting with an alphanumeric + return 1 + fi +} + +function check_git_branch_name_format { + BUILD_BRANCH="$1" + if [ "$BUILD_BRANCH" != 'main' ] && ! [[ $BUILD_BRANCH =~ (hotfix|task)\/(saet)-([0-9]{1,5})(_|-)([A-Za-z0-9])([A-Za-z0-9_-]{9,45})$ ]] ; then + echo 1 + fi +} + +BRANCH_NAME=${BRANCH_NAME:-$(git rev-parse --abbrev-ref HEAD)} +check_git_branch_name "$BRANCH_NAME" + +[ $? != 0 ] && exit_code=1 ||: +exit $exit_code diff --git a/scripts/githooks/check-english-usage.sh b/scripts/githooks/check-english-usage.sh index 5297e084..5a5a416f 100755 --- a/scripts/githooks/check-english-usage.sh +++ b/scripts/githooks/check-english-usage.sh @@ -1,4 +1,3 @@ - #!/bin/bash # WARNING: Please, DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. From c3e48043b54cf75dcb216d1dd8777ccc7bcc02c6 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 15:46:45 +0000 Subject: [PATCH 051/181] Added extra information into the pre commit file --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) create mode 120000 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 120000 index 00000000..4932d523 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1 @@ +scripts/config/pre-commit.yaml \ No newline at end of file From 31e6e53a0d09d7081da8d8c3e1a1a59415f959cd Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 15:47:47 +0000 Subject: [PATCH 052/181] Added extra information into the pre commit file --- infrastructure/account_wide.tfvars | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index fcf22227..f4325745 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -9,3 +9,4 @@ flow_log_s3_versioning = false flow_logs_s3_expiration_days = 10 enable_s3_kms_encryption = true + From df20d281b724b316f04147d8f5d52a2418410282 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 30 Jan 2026 15:49:05 +0000 Subject: [PATCH 053/181] Added extra information into the pre commit file --- infrastructure/account_wide.tfvars | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/account_wide.tfvars b/infrastructure/account_wide.tfvars index f4325745..fcf22227 100644 --- a/infrastructure/account_wide.tfvars +++ b/infrastructure/account_wide.tfvars @@ -9,4 +9,3 @@ flow_log_s3_versioning = false flow_logs_s3_expiration_days = 10 enable_s3_kms_encryption = true - From d77ace3bbb4b9c79043657d57786a1e7537c3400 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 2 Feb 2026 16:06:59 +0000 Subject: [PATCH 054/181] Adding the deploy application and triage files --- .github/workflows/build-project.yaml | 149 +++++++++ .../deploy-application-infrastructure.yaml | 186 +++++++++++ .../deploy-data-migration-project.yaml | 88 ++++++ .../pipeline-deploy-application.yaml | 179 +---------- infrastructure/stacks/triage/dynamodb.tf | 33 ++ infrastructure/stacks/triage/iam.tf | 75 +++++ infrastructure/stacks/triage/lambda.tf | 23 ++ infrastructure/stacks/triage/restapi.tf | 79 +++++ infrastructure/stacks/triage/s3.tf | 8 + infrastructure/stacks/triage/trigger.tf | 28 ++ infrastructure/stacks/triage/variables.tf | 297 ++++++++++++++++++ scripts/workflow/generate-feature-flags.sh | 64 ++++ 12 files changed, 1032 insertions(+), 177 deletions(-) create mode 100644 .github/workflows/build-project.yaml create mode 100644 .github/workflows/deploy-application-infrastructure.yaml create mode 100644 .github/workflows/deploy-data-migration-project.yaml create mode 100644 infrastructure/stacks/triage/dynamodb.tf create mode 100644 infrastructure/stacks/triage/iam.tf create mode 100644 infrastructure/stacks/triage/lambda.tf create mode 100644 infrastructure/stacks/triage/restapi.tf create mode 100644 infrastructure/stacks/triage/s3.tf create mode 100644 infrastructure/stacks/triage/trigger.tf create mode 100644 infrastructure/stacks/triage/variables.tf create mode 100644 scripts/workflow/generate-feature-flags.sh diff --git a/.github/workflows/build-project.yaml b/.github/workflows/build-project.yaml new file mode 100644 index 00000000..363ed522 --- /dev/null +++ b/.github/workflows/build-project.yaml @@ -0,0 +1,149 @@ +name: Build project workflow +run-name: Build ${{ inputs.type }} - ${{ inputs.name }} + +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + build_type: + description: "The type of project to build (service, package)" + required: true + type: string + name: + description: "The name of the package to build" + required: true + type: string + python_version: + description: "The version of Python" + required: true + type: string + commit_hash: + description: "The commit hash, set by the CI/CD pipeline workflow" + required: false + type: string + environment: + description: "The deployment environment" + required: true + type: string + repo_name: + description: "The name of the Git repo" + required: true + type: string + workspace: + description: "The name of the workspace to deploy the infrastructure into" + required: true + type: string + application_tag: + description: "The application tag identifying the timeline in the repository to deploy from" + required: false + type: string + type: + description: "The type of permissions (e.g., account, app)" + required: true + type: string + release_build: + description: "Flag to indicate if this is a release build" + required: false + type: boolean + default: false + +jobs: + build-project: + name: "Build ${{ inputs.build_type }} - ${{ inputs.name }}" + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.application_tag && inputs.application_tag != 'latest' && inputs.application_tag || '' }} + + - name: "Install asdf" + uses: asdf-vm/actions/setup@v4.0.1 + with: + asdf_branch: v0.15.0 + + - name: "Cache asdf tools" + id: asdf-cache + uses: actions/cache@v5 + with: + path: ~/.asdf + key: asdf-${{ runner.os }}-${{ hashFiles('.tool-versions') }} + + - name: "Install tools from .tool-versions" + if: steps.asdf-cache.outputs.cache-hit != 'true' + uses: asdf-vm/actions/install@v4.0.1 + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Get directories" + id: get-directories + run: | + if [[ "${{ inputs.build_type }}" == "service" ]]; then + echo "working_directory=services/${{ inputs.name }}" >> $GITHUB_OUTPUT + echo "build_directory=build/services/${{ inputs.name }}" >> $GITHUB_OUTPUT + elif [[ "${{ inputs.build_type }}" == "package" ]]; then + echo "working_directory=application/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT + echo "build_directory=build/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT + else + echo "Invalid build type: ${{ inputs.build_type }}" + exit 1 + fi + + - name: "Install project" + run: make install + working-directory: ${{ steps.get-directories.outputs.working_directory }} + + - name: "Run linting" + run: make lint + working-directory: ${{ steps.get-directories.outputs.working_directory }} + + - name: "Run unit tests" + run: make unit-test + working-directory: ${{ steps.get-directories.outputs.working_directory }} + + - name: "Publish coverage report to GitHub" + uses: actions/upload-artifact@v6 + with: + name: coverage-${{ inputs.name }}.xml + path: ${{ steps.get-directories.outputs.working_directory }}/coverage-${{ inputs.name }}.xml + if-no-files-found: ignore + + - name: "Build project" + run: make build + working-directory: ${{ steps.get-directories.outputs.working_directory }} + env: + SERVICE: ${{ inputs.name }} + PACKAGE: ${{ inputs.name }} + PYTHON_VERSION: ${{ inputs.python_version }} + APPLICATION_TAG: ${{ inputs.application_tag }} + REPO_NAME: ${{ inputs.repo_name }} + + - name: "Publish artefacts to S3" + run: make publish + working-directory: ${{ steps.get-directories.outputs.working_directory }} + env: + SERVICE: ${{ inputs.name }} + PACKAGE: ${{ inputs.name }} + COMMIT_HASH: ${{ inputs.commit_hash }} + ENVIRONMENT: ${{ inputs.environment }} + REPO_NAME: ${{ inputs.repo_name }} + WORKSPACE: ${{ inputs.workspace }} + APPLICATION_TAG: ${{ inputs.application_tag }} + RELEASE_BUILD: ${{ inputs.release_build }} + + - name: "Publish artefacts to GitHub" + uses: actions/upload-artifact@v6 + with: + name: ${{ inputs.name }}-${{ inputs.build_type }}-artefacts + path: ${{ steps.get-directories.outputs.build_directory }} + if-no-files-found: error diff --git a/.github/workflows/deploy-application-infrastructure.yaml b/.github/workflows/deploy-application-infrastructure.yaml new file mode 100644 index 00000000..17d94019 --- /dev/null +++ b/.github/workflows/deploy-application-infrastructure.yaml @@ -0,0 +1,186 @@ +name: Deploy application infrastructure workflow + +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + environment: + description: "The name of the environment to deploy the infrastructure into" + required: true + type: string + workspace: + description: "The name of the workspace to deploy the infrastructure into" + required: true + default: "default" + type: string + project: + description: "The project - eg dos or cm." + required: false + default: "dos" + type: string + tag: + description: "The git tag identifying the timeline in the repository to deploy from" + required: false + type: string + release_tag: + description: "The git tag identifying the timeline in the repository to deploy from" + required: false + type: string + application_tag: + description: "The application tag identifying the timeline in the repository to deploy from" + required: false + type: string + workflow_timeout: + description: "Timeout duration in minutes" + required: false + default: 5 + type: number + commit_hash: + description: "The commit hash, set by the CI/CD pipeline workflow" + required: false + type: string + type: + description: "The type of permissions (e.g., account, app)" + required: false + default: "app" + type: string + stacks: + description: "A list of the infrastructure stacks to deploy from the domain. If not supplied, no infrastructure will be deployed" + required: false + default: "['database', 'crud_apis', 'data_migration', 'read_only_viewer', 'opensearch', 'etl_ods', 'dos_search', 'is_performance', 'ui', 'app_config']" + type: string + outputs: + plan_result: + description: "The Terraform plan output" + value: ${{ jobs.plan-application-infrastructure.outputs.plan_result }} + deploy_status: + description: "The status of the deployment" + value: ${{ jobs.deploy_summary.outputs.deploy_status }} + + +jobs: + # Generate AppConfig artifacts (feature flags JSON, ensure tfvars) and publish for downstream jobs + prepare-appconfig: + name: "Prepare AppConfig artifacts" + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: "Install Python dependencies" + run: | + python -m pip install --upgrade pip + pip install pyyaml + + - name: "Generate feature flags JSON" + env: + ENVIRONMENT: ${{ inputs.workspace == 'default' && inputs.environment || 'workspace' }} + run: | + /bin/bash ./scripts/workflow/generate-feature-flags.sh + + - name: "Upload AppConfig feature flags" + uses: actions/upload-artifact@v6 + with: + name: appconfig_feature_flags_${{ inputs.workspace == 'default' && inputs.environment || 'workspace' }} + path: infrastructure/toggles/feature-flags.json + + plan-application-infrastructure: + name: "Plan application infrastructure deployment to ${{ inputs.environment }} " + concurrency: + group: "${{ inputs.environment }}-${{ inputs.tag || inputs.workspace}}" + cancel-in-progress: false + needs: + - prepare-appconfig + uses: ./.github/workflows/deploy-infrastructure.yaml + with: + environment: ${{ inputs.environment }} + workspace: ${{ inputs.workspace }} + stacks: ${{ inputs.stacks }} + application_tag: ${{ inputs.application_tag }} + commit_hash: ${{ inputs.commit_hash }} + tag: ${{ inputs.tag }} + release_tag: ${{ inputs.release_tag }} + action: plan + type: ${{ inputs.type }} + workflow_timeout: ${{ inputs.workflow_timeout }} + download_appconfig_artifact: true + appconfig_artifact_name: "appconfig_feature_flags" + secrets: inherit + + manual-approval-application-infra: + name: "Manual approval for deployment of application infrastructure to the ${{ inputs.environment }} environment" + if: ${{ needs.plan-application-infrastructure.outputs.plan_result == 'true' }} + needs: + - plan-application-infrastructure + runs-on: ubuntu-latest + environment: "${{ inputs.environment }}-protected" + outputs: + approved: ${{ steps.log-approval.outputs.approved }} + steps: + - name: Approval required + run: echo "${{ inputs.environment }} deployment paused for manual approval. Please approve in the Actions tab." + - name: Log approval + id: log-approval + run: echo "approved=true" >> $GITHUB_OUTPUT + + apply-application-infrastructure: + name: "Apply application infrastructure deployment to ${{ inputs.environment }}" + concurrency: + group: "${{ inputs.environment }}-${{ inputs.tag || inputs.workspace}}" + cancel-in-progress: false + needs: + - manual-approval-application-infra + uses: ./.github/workflows/deploy-infrastructure.yaml + with: + environment: ${{ inputs.environment }} + workspace: ${{ inputs.workspace }} + stacks: ${{ inputs.stacks }} + application_tag: ${{ inputs.application_tag }} + commit_hash: ${{ inputs.commit_hash }} + tag: ${{ inputs.tag }} + release_tag: ${{ inputs.release_tag }} + action: apply + type: ${{ inputs.type }} + workflow_timeout: ${{ inputs.workflow_timeout }} + download_appconfig_artifact: true + appconfig_artifact_name: "appconfig_feature_flags" + secrets: inherit + + deploy_summary: + name: "Summarise deployment of application infrastructure to ${{ inputs.environment }} environment" + needs: + - plan-application-infrastructure + - manual-approval-application-infra + - apply-application-infrastructure + if: always() && !cancelled() + runs-on: ubuntu-latest + outputs: + deploy_status: ${{ steps.deployment_summary.outputs.deploy_status }} + steps: + - name: Deployment Summary + id: deployment_summary + run: | + if [ ${{ needs.manual-approval-application-infra.result }} == 'skipped' ]; then + echo "Plan identified no changes for application infrastructure in the ${{ inputs.environment }} environment." + echo "deploy_status=Success" >> $GITHUB_OUTPUT + else + if [ ${{ needs.plan-application-infrastructure.outputs.plan_result }} == 'true' ]; then + if [ ${{ needs.manual-approval-application-infra.outputs.approved }} == 'true' ]; then + if [ "${{ needs.apply-application-infrastructure.result }}" == "success" ]; then + echo "Changes APPROVED and successfully APPLIED to application infrastructure in the ${{ inputs.environment }} environment." + echo "deploy_status=Success" >> $GITHUB_OUTPUT + else + echo "Changes APPROVED but were NOT successfully applied to application infrastructure in the ${{ inputs.environment }} environment." + echo "deploy_status=Failed" >> $GITHUB_OUTPUT + fi + else + echo "Planned changes to application infrastructure REJECTED for deployment to the ${{ inputs.environment }} environment." + echo "deploy_status=Rejected" >> $GITHUB_OUTPUT + fi + fi + fi diff --git a/.github/workflows/deploy-data-migration-project.yaml b/.github/workflows/deploy-data-migration-project.yaml new file mode 100644 index 00000000..305f93bd --- /dev/null +++ b/.github/workflows/deploy-data-migration-project.yaml @@ -0,0 +1,88 @@ +name: Deploy data migration project workflow +run-name: Deploy ${{ inputs.build_type }} - ${{ inputs.name }} +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + build_type: + description: "The type of project to build (service, package)" + required: true + type: string + name: + description: "The name of the package to build" + required: true + type: string + python_version: + description: "The version of Python" + required: true + type: string + commit_hash: + description: "The commit hash, set by the CI/CD pipeline workflow" + required: false + type: string + environment: + description: "The deployment environment" + required: true + type: string + repo_name: + description: "The name of the Git repo" + required: true + type: string + workspace: + description: "The name of the workspace to deploy the infrastructure into" + required: true + type: string + application_tag: + description: "The application tag identifying the timeline in the repository to deploy from" + required: false + type: string + type: + description: "The type of permissions (e.g., account, app)" + required: true + type: string + +jobs: + deploy-project: + name: "Deploy step for ${{ inputs.build_type }} - ${{ inputs.name }}" + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Get directories" + id: get-directories + run: | + if [[ "${{ inputs.build_type }}" == "service" ]]; then + echo "working_directory=services/${{ inputs.name }}" >> $GITHUB_OUTPUT + echo "build_directory=build/services/${{ inputs.name }}" >> $GITHUB_OUTPUT + elif [[ "${{ inputs.build_type }}" == "package" ]]; then + echo "working_directory=application/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT + echo "build_directory=build/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT + else + echo "Invalid build type: ${{ inputs.build_type }}" + exit 1 + fi + + - name: "Invoke DMS Replication Tasks" + run: make invoke-replication-task + working-directory: ${{ steps.get-directories.outputs.working_directory }} + env: + SERVICE: ${{ inputs.name }} + PACKAGE: ${{ inputs.name }} + COMMIT_HASH: ${{ inputs.commit_hash }} + ENVIRONMENT: ${{ inputs.environment }} + REPO_NAME: ${{ inputs.repo_name }} + WORKSPACE: ${{ inputs.workspace }} + APPLICATION_TAG: ${{ inputs.application_tag }} diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 4cc7028b..4f0645ea 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -44,7 +44,7 @@ jobs: with: environment: ${{ needs.metadata.outputs.environment }} workspace: ${{ needs.metadata.outputs.workspace }} - stacks: "['database', 'crud_apis', 'data_migration', 'read_only_viewer', 'opensearch', 'etl_ods', 'dos_search', 'is_performance', 'ui']" + stacks: "['triage']" type: app build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} secrets: inherit @@ -59,17 +59,7 @@ jobs: include: - name: "python" build_type: "package" - - name: "crud-apis" - build_type: "service" - - name: "data-migration" - build_type: "service" - - name: "read-only-viewer" - build_type: "service" - - name: "etl-ods" - build_type: "service" - - name: "dos-search" - build_type: "service" - - name: "dos-ui" + - name: "triage" build_type: "service" uses: ./.github/workflows/build-project.yaml with: @@ -77,32 +67,12 @@ jobs: build_type: ${{ matrix.build_type }} python_version: ${{ needs.metadata.outputs.python_version }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.mgmt_environment }} repo_name: ${{ needs.metadata.outputs.reponame }} workspace: ${{ needs.metadata.outputs.workspace }} application_tag: ${{ inputs.application_tag || 'latest' }} type: app secrets: inherit - build-sandbox-containers: - name: "Build container ${{ matrix.name }}" - needs: - - metadata - - quality-checks - strategy: - matrix: - include: - - name: "sandbox-dos-search" - uses: ./.github/workflows/build-sandbox-images.yaml - with: - name: ${{ matrix.name }} - commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.environment }} - application_tag: ${{ inputs.application_tag || 'latest' }} - secrets: - ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} - PROXYGEN_URL: ${{ secrets.PROXYGEN_URL }} - perform-code-analysis: name: "Perform static code analysis" needs: @@ -129,102 +99,6 @@ jobs: workflow_timeout: 30 secrets: inherit - deploy-proxy-to-apim: - name: "Deploy ${{ matrix.api_name }} API to Proxygen" - needs: - - metadata - - deploy-application-infrastructure - uses: ./.github/workflows/authenticate-and-deploy-to-apim.yaml - strategy: - matrix: - api_name: - - dos-search - # Add more API names here as needed - with: - environment: ${{ needs.metadata.outputs.environment }} - workspace: ${{ needs.metadata.outputs.workspace }} - api_name: ${{ matrix.api_name }} - apim_env: ${{ needs.metadata.outputs.environment == 'dev' && 'internal-dev' || needs.metadata.outputs.environment == 'test' && 'internal-qa' || needs.metadata.outputs.environment }} - secrets: - ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} - AWS_REGION: ${{ vars.AWS_REGION }} - PROXYGEN_URL: ${{ secrets.PROXYGEN_URL }} - - # TODO : FTRS-1899 Re-enable data migration step once ETL process is stable - # migrate-data: - # name: "Run ETL process in ${{ needs.metadata.outputs.environment }}" - # if: github.ref == 'refs/heads/main' && needs.metadata.outputs.environment != 'prod' - # concurrency: - # group: "${{ needs.metadata.outputs.environment }}-${{ needs.metadata.outputs.workspace }}" - # cancel-in-progress: false - # needs: - # - metadata - # - deploy-application-infrastructure - # uses: ./.github/workflows/migrate-data.yaml - # with: - # environment: ${{ needs.metadata.outputs.environment }} - # function_name: "ftrs-dos-${{ needs.metadata.outputs.environment }}-data-migration-queue-populator-lambda" - # queue_name: "ftrs-dos-${{ needs.metadata.outputs.environment }}-data-migration-dms-events" - # type: app - # secrets: inherit - - export-dynamodb-to-s3: - name: "Export DynamoDB tables to S3 in ${{ needs.metadata.outputs.environment }}" - if: github.ref == 'refs/heads/main' && needs.metadata.outputs.environment != 'prod' - concurrency: - group: "${{ needs.metadata.outputs.environment }}-${{ needs.metadata.outputs.workspace }}" - cancel-in-progress: false - needs: - - metadata - - deploy-application-infrastructure - # - migrate-data - uses: ./.github/workflows/manage-dynamodb-data.yaml - with: - environment: ${{ needs.metadata.outputs.environment }} - type: app - action: export - secrets: inherit - - restore-dynamodb-from-s3: - name: "Restore data from S3 to DynamoDB tables in ${{ needs.metadata.outputs.workspace }}" - if: needs.metadata.outputs.workspace != 'default' && needs.metadata.outputs.environment != 'prod' - needs: - - metadata - - deploy-application-infrastructure - uses: ./.github/workflows/manage-dynamodb-data.yaml - with: - environment: ${{ needs.metadata.outputs.environment }} - workspace: ${{ needs.metadata.outputs.workspace }} - action: import - type: app - secrets: inherit - - deploy-frontend-services: - name: "Deploy ${{ matrix.name }} to ${{ needs.metadata.outputs.environment }}" - concurrency: - group: "${{ needs.metadata.outputs.environment }}-${{ needs.metadata.outputs.workspace }}-${{ matrix.name }}" - cancel-in-progress: false - needs: - - metadata - - deploy-application-infrastructure - strategy: - matrix: - include: - - name: "dos-ui" - - name: "read-only-viewer" - uses: ./.github/workflows/deploy-frontend-project.yaml - with: - name: ${{ matrix.name }} - build_type: "service" - python_version: ${{ needs.metadata.outputs.python_version }} - commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.environment }} - repo_name: ${{ needs.metadata.outputs.reponame }} - workspace: ${{ needs.metadata.outputs.workspace }} - application_tag: ${{ inputs.application_tag || 'latest' }} - type: "app" - secrets: inherit - deploy-data-migration-service: name: "Deploy data migration service to ${{ needs.metadata.outputs.environment }}" if: github.ref == 'refs/heads/main' && needs.metadata.outputs.environment != 'prod' @@ -247,34 +121,6 @@ jobs: type: "app" secrets: inherit - service-automation-tests: - name: "Run service automation tests on ${{ needs.metadata.outputs.environment }}" - needs: - - metadata - - deploy-application-infrastructure - - restore-dynamodb-from-s3 - - export-dynamodb-to-s3 - if: | - always() && - !cancelled() && - ( - needs.restore-dynamodb-from-s3.result == 'success' || - ( - needs.restore-dynamodb-from-s3.result == 'skipped' && - (needs.export-dynamodb-to-s3.result == 'success' || needs.deploy-application-infrastructure.result == 'success') - ) - ) - uses: ./.github/workflows/service-automation-test.yaml - with: - environment: ${{ needs.metadata.outputs.environment }} - workspace: ${{ needs.metadata.outputs.workspace }} - commit_hash: ${{ needs.metadata.outputs.commit_hash }} - tag: ${{ inputs.tag }} - test_tag: "ftrs-pipeline" - test_type: "api" - type: app - secrets: inherit - generate-prerelease: name: "Generate prerelease tag" needs: @@ -298,24 +144,3 @@ jobs: - export-dynamodb-to-s3 if: always() uses: ./.github/workflows/pipeline-status-check.yaml - - slack-notifications: - name: "Send Notification to Slack" - needs: - - metadata - - quality-checks - - build-services - - deploy-application-infrastructure - # - migrate-data - - export-dynamodb-to-s3 - - restore-dynamodb-from-s3 - - deploy-frontend-services - - deploy-data-migration-service - - service-automation-tests - - generate-prerelease - - check-pipeline-status - if: always() - uses: ./.github/workflows/slack-notifications.yaml - with: - env: ${{ needs.metadata.outputs.environment }} - secrets: inherit diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf new file mode 100644 index 00000000..e701c9b8 --- /dev/null +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -0,0 +1,33 @@ +module "startingnode" { + source = "../../modules/dynamodb" + table_name = "${var.project_name}-${var.environment}-Starting-Node" + hash_key = "Skillset" + range_key = "GenderAgeParty" + + attributes = [ + { hash_key = "Skillset", type = "S" }, + { hash_key = "Skillset", type = "S" } + ] +} + +module "triagenode" { + source = "../../modules/dynamodb" + table_name = "${var.project_name}-${var.environment}-Triage-Node" + hash_key = "Coordinate" + + attributes = [{ + name = "Coordinate" + type = "S" + }] +} + +module "bodymap" { + source = "../../modules/dynamodb" + table_name = "${var.project_name}-${var.environment}-Body-Map-Node" + hash_key = "id" + + attributes = [{ + name = "id" + type = "S" + }] +} diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf new file mode 100644 index 00000000..b1de91c1 --- /dev/null +++ b/infrastructure/stacks/triage/iam.tf @@ -0,0 +1,75 @@ +resource "aws_iam_role" "lambda_role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Principal = { + Service = var.assume_services + }, + Action = "sts:AssumeRole" + }] + }) + name = var.name +} + +resource "aws_iam_policy" "s3_access" { + name = "${var.name}-s3-access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + Resource = [ + # bucket itself + "arn:aws:s3:::${var.bucket_name}", + # all objects inside + "arn:aws:s3:::${var.bucket_name}/*" + ] + }] + }) +} + +resource "aws_iam_policy" "ddb_access" { + name = "${var.name}-dynamo-db-access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:Query", + "dynamodb:Scan" + ], + Resource = concat( + var.table_arns, + [for q in var.table_arns : "${q}/index/*"] + ) + }] + }) +} + +resource "aws_iam_role_policy_attachment" "s3_attach" { + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.s3_access.arn +} + +resource "aws_iam_role_policy_attachment" "ddb_aatach" { + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.ddb_access.arn +} + +resource "aws_iam_role_policy_attachment" "attachments" { + for_each = toset(var.policy_arns) + + role = aws_iam_role.lambda_role.name + policy_arn = each.value +} + diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf new file mode 100644 index 00000000..34842d55 --- /dev/null +++ b/infrastructure/stacks/triage/lambda.tf @@ -0,0 +1,23 @@ + +module "s3lambda" { + source = "../../modules/lambda" + aws_region = var.aws_region + function_name = "${var.project_name}-${var.environment}s3-Lambda" + policy_jsons = var.policy_jsons + handler = "s3_configurator.handler" + s3_bucket_name = module.SAETBucket.bucket_name + runtime = var.runtime + s3_key = var.s3_key + memory_size = var.mem_size + body_map_table = module.bodymap.table_name + starting_node_table = module.startingnode.table_name + triage_node_table = module.triagenode.table_name + + + environment_variables = { + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "PROJECT_NAME" = var.project + } + +} diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf new file mode 100644 index 00000000..9bbf595b --- /dev/null +++ b/infrastructure/stacks/triage/restapi.tf @@ -0,0 +1,79 @@ +resource "aws_api_gateway_rest_api" "triage" { + name = var.name +} + +resource "aws_api_gateway_resource" "resource" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + parent_id = aws_api_gateway_rest_api.triage.root_resource_id + path_part = each.value.path_part +} + +resource "aws_api_gateway_method" "methods" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = each.value.method + authorization = var.authorization +} + +# Create method responses for the API methods +resource "aws_api_gateway_method_response" "method_response" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + status_code = "200" +} + +resource "aws_api_gateway_integration" "integrations" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + integration_http_method = var.int_http_method + type = var.type + uri = each.value.lambda_invoke_arn +} + +# Create integration responses for the API methods +resource "aws_api_gateway_integration_response" "integration_response" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + status_code = aws_api_gateway_method_response.method_response[each.key].status_code + selection_pattern = "" +} + +# Create a deployment to publish the API +resource "aws_api_gateway_deployment" "deployment" { + rest_api_id = aws_api_gateway_rest_api.triage.id + + depends_on = [ + aws_api_gateway_integration.integrations, + aws_api_gateway_integration_response.integration_response, + aws_api_gateway_method.methods + ] + + # Force redeployment when any API resources change + triggers = { + redeployment = sha1(jsonencode({ + resources = aws_api_gateway_resource.resource + methods = aws_api_gateway_method.methods + integrations = aws_api_gateway_integration.integrations + integration_responses = aws_api_gateway_integration_response.integration_response + })) + } + + lifecycle { + create_before_destroy = true + } +} + +# Create a stage (environment) for the API +resource "aws_api_gateway_stage" "stage" { + deployment_id = aws_api_gateway_deployment.deployment.id + rest_api_id = aws_api_gateway_rest_api.triage.id + stage_name = var.stage_name +} diff --git a/infrastructure/stacks/triage/s3.tf b/infrastructure/stacks/triage/s3.tf new file mode 100644 index 00000000..8e002d2d --- /dev/null +++ b/infrastructure/stacks/triage/s3.tf @@ -0,0 +1,8 @@ +resource "aws_s3_bucket" "storage" { + bucket = var.bucket_name + region = var.region + + lifecycle { + prevent_destroy = true + } +} diff --git a/infrastructure/stacks/triage/trigger.tf b/infrastructure/stacks/triage/trigger.tf new file mode 100644 index 00000000..bc82a7cd --- /dev/null +++ b/infrastructure/stacks/triage/trigger.tf @@ -0,0 +1,28 @@ +resource "aws_lambda_permission" "allows3" { + count = var.bucket_arn != null ? 1 : 0 + statement_id = var.statement_id + action = var.action + function_name = var.lambda_name + principal = var.principal + source_arn = var.bucket_arn +} + +resource "aws_s3_bucket_notification" "bucket_notification" { + count = var.bucket_name != null ? 1 : 0 + bucket = var.bucket_name + lambda_function { + lambda_function_arn = var.lambda_arn + events = var.events + #filter_prefix = var.filter_prefix + } + depends_on = [aws_lambda_permission.allows3] +} + +resource "aws_lambda_permission" "allowapig" { + count = var.api_gateway_source_arn != null ? 1 : 0 + statement_id = var.statement_id + action = var.action + function_name = var.lambda_name + principal = var.principal + source_arn = var.api_gateway_source_arn +} diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf new file mode 100644 index 00000000..c2aeb498 --- /dev/null +++ b/infrastructure/stacks/triage/variables.tf @@ -0,0 +1,297 @@ +# Dynamo Db vars +variable "triage_table_name" { + type = string +} + +variable "billing" { + type = string + default = "PAY_PER_REQUEST" +} + +variable "hash_key" { + description = "primary partition key" + type = string +} +variable "hash_key_type" { + description = "partition key type" + type = string + default = "S" +} + +variable "range_key" { + description = "sort key" + type = string +} + +variable "aws_region" { + description = "sort key" + type = string +} + +variable "environment" { + description = "sort key" + type = string +} + +variable "project_name" { + description = "sort key" + type = string +} + +# IAM vars +data "aws_iam_policy_document" "iam_policy" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +variable "name" { + type = string +} + +variable "assume_services" { + description = "AWS Sewrrvices a;;pwed to assume this role" + type = list(string) +} + +variable "policy_arns" { + description = "List of policy ARNS to attach" + type = list(string) +} + +variable "bucket_name" { + description = "s3 bucket we are accessing" + type = string +} + +variable "table_arns" { + description = "Dynamodb arns" + type = list(string) +} + +variable "aws_region" { + description = "sort key" + type = string +} + +variable "environment" { + description = "sort key" + type = string +} + +variable "project_name" { + description = "sort key" + type = string +} + +# Lambda vars +variable "function_name" { + type = string +} + +variable "handler" { + type = string +} + +variable "runtime" { + type = string +} + +variable "bucket_name" { + type = string +} + +variable "s3_key" { + type = string +} + +variable "lambda_role" { + type = string +} + +variable "mem_size" { + type = number +} + +# Optional environment variables for DynamoDB tables +variable "body_map_table" { + type = string + default = null +} + +variable "starting_node_table" { + type = string + default = null +} + +variable "triage_node_table" { + type = string + default = null +} + +variable "project_name" { + type = string + default = null +} + +variable "aws_region" { + type = string + default = null +} + +variable "environment" { + type = string + default = null +} + +# REST API vars +variable "name" { + type = string +} + +variable "resources" { + type = list(object({ + path_part = string + method = string + lambda_invoke_arn = string + })) + +} +variable "authorization" { + type = string +} + +variable "type" { + type = string + default = "AWS_PROXY" +} + +variable "int_http_method" { + type = string + default = "POST" +} + +variable "aws_region" { + description = "sort key" + type = string +} + +variable "environment" { + description = "sort key" + type = string +} + +variable "project_name" { + description = "sort key" + type = string +} + +variable "stage_name" { + type = string +} + +# s3 vars +variable "bucket_name" { + description = "Name of the S3 bucket" + type = string +} + +variable "region" { + description = "AWS region for S3 bucket" + type = string +} + +variable "aws_region" { + description = "sort key" + type = string +} + +variable "environment" { + description = "sort key" + type = string +} + +variable "project_name" { + description = "sort key" + type = string +} + +# Trigger vars +variable "statement_id" { + type = string +} + +variable "action" { + type = string +} + +variable "lambda_name" { + type = string +} + +variable "principal" { + type = string +} + +variable "bucket_name" { + type = string + default = null +} + +variable "bucket_arn" { + type = string + default = null +} + +variable "lambda_arn" { + type = string +} + +variable "events" { + type = list(string) + default = [] +} + +variable "filter_prefix" { + type = string + default = null +} + +variable "api_gateway_source_arn" { + type = string + default = null +} + +variable "aws_region" { + description = "sort key" + type = string +} + +variable "environment" { + description = "sort key" + type = string +} + +variable "project_name" { + description = "sort key" + type = string +} + +variable "policy_jsons" { + description = "List of JSON policies for Lambda" + default = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Principal = { + Service = var.assume_services + }, + Action = "sts:AssumeRole" + }] + }) +} diff --git a/scripts/workflow/generate-feature-flags.sh b/scripts/workflow/generate-feature-flags.sh new file mode 100644 index 00000000..161f6d13 --- /dev/null +++ b/scripts/workflow/generate-feature-flags.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +# Script to generate AWS AppConfig Feature Flags JSON from the toggle registry +# This script reads the toggle registry YAML and generates environment-specific feature flags + +# fail on first error +set -e + +# Determine script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Default values +TOGGLE_REGISTRY_FILE="${TOGGLE_REGISTRY_FILE:-"$ROOT_DIR/infrastructure/toggles/toggle-registry.yaml"}" +OUTPUT_FILE="${OUTPUT_FILE:-"$ROOT_DIR/infrastructure/toggles/feature-flags.json"}" +CREATED_DATE="${CREATED_DATE:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}" + +# Validate required environment variables +if [ -z "$ENVIRONMENT" ]; then + echo "Error: ENVIRONMENT environment variable is required" + echo "Usage: ENVIRONMENT=dev $0" + exit 1 +fi + +# Check if toggle registry file exists +if [ ! -f "$TOGGLE_REGISTRY_FILE" ]; then + echo "Error: Toggle registry file not found: $TOGGLE_REGISTRY_FILE" + exit 1 +fi + +echo "======================================" +echo "Generating Feature Flags" +echo "======================================" +echo "Environment: $ENVIRONMENT" +echo "Toggle Registry: $TOGGLE_REGISTRY_FILE" +echo "Output File: $OUTPUT_FILE" +echo "Created Date: $CREATED_DATE" +echo "======================================" + +# Generate the feature flags JSON using Python script +GENERATED_FILE=$(ENVIRONMENT="$ENVIRONMENT" \ + TOGGLE_REGISTRY_FILE="$TOGGLE_REGISTRY_FILE" \ + OUTPUT_FILE="$OUTPUT_FILE" \ + CREATED_DATE="$CREATED_DATE" \ + python3 "$SCRIPT_DIR/generate_feature_flags.py") + +if [ $? -eq 0 ]; then + echo "✓ Feature flags generated successfully: $GENERATED_FILE" + + # Display a summary of the generated file + if [ -f "$GENERATED_FILE" ]; then + FLAG_COUNT=$(python3 -c "import json; f=open('$GENERATED_FILE'); data=json.load(f); print(len(data.get('flags', {})))") + echo "✓ Total flags generated: $FLAG_COUNT" + + # Show enabled flags + ENABLED_COUNT=$(python3 -c "import json; f=open('$GENERATED_FILE'); data=json.load(f); print(sum(1 for v in data.get('values', {}).values() if v.get('enabled')))") + echo "✓ Flags enabled in $ENVIRONMENT: $ENABLED_COUNT" + fi +else + echo "✗ Failed to generate feature flags" + exit 1 +fi + +echo "======================================" From f192cd0b1d432910f8d388e99a19d5b0bd60b95f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 08:29:57 +0000 Subject: [PATCH 055/181] Adding the deploy application and triage files --- .github/workflows/pipeline-deploy-application.yaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 4f0645ea..19031df6 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -121,20 +121,6 @@ jobs: type: "app" secrets: inherit - generate-prerelease: - name: "Generate prerelease tag" - needs: - - deploy-frontend-services - - service-automation-tests - if: > - always() && - github.event_name == 'push' && - github.ref == 'refs/heads/main' && - needs.deploy-frontend-services.result == 'success' && - needs.service-automation-tests.result == 'success' - uses: ./.github/workflows/generate-prerelease.yaml - secrets: inherit - check-pipeline-status: name: "Check Pipeline Status" needs: From d7822461f43bd2144e8e81f2a6db9b3c9846caba Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 08:33:24 +0000 Subject: [PATCH 056/181] Adding the deploy application and triage files --- .github/workflows/pipeline-deploy-application.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 19031df6..a0faad76 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -124,9 +124,6 @@ jobs: check-pipeline-status: name: "Check Pipeline Status" needs: - - deploy-frontend-services - deploy-data-migration-service - - service-automation-tests - - export-dynamodb-to-s3 if: always() uses: ./.github/workflows/pipeline-status-check.yaml From eda39e419600ace63cf8a398d98555fa280001a1 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 08:36:56 +0000 Subject: [PATCH 057/181] Adding the deploy application and triage files --- .github/workflows/pipeline-status-check.yaml | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/pipeline-status-check.yaml diff --git a/.github/workflows/pipeline-status-check.yaml b/.github/workflows/pipeline-status-check.yaml new file mode 100644 index 00000000..d5d5c5bd --- /dev/null +++ b/.github/workflows/pipeline-status-check.yaml @@ -0,0 +1,32 @@ +name: Pipeline outcome check + +permissions: {} +on: + workflow_call: + + +jobs: + check-pipeline-status: + name: "Check Pipeline Status" + runs-on: ubuntu-latest + steps: + - uses: martialonline/workflow-status@v4 + id: check + + - name: "Debug Info" + run: | + echo "Job Status: ${{ job.status }}" + echo "GitHub Event: ${{ github.event_name }}" + echo "Workflow Run: ${{ toJson(github.event.workflow_run) }}" + echo "Workflow Status: ${{ github.event.workflow_run.conclusion }}" + echo "GitHub Actor: ${{ github.actor }}" + echo "Pull Request URL: ${{ github.event.pull_request.html_url || github.event.head_commit.url }}" + echo "Status: ${{ steps.check.outputs.status }}" + + - name: "Fail if workflow not completed successfully" + if: ${{ steps.check.outputs.status != 'success' }} + run: | + echo "Workflow not completed successfully." + echo "Run ID: ${{ github.run_id }}" + exit 1 + From feacfb5cd9e05b9db1581e5481d43d61f5f878bb Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 08:40:14 +0000 Subject: [PATCH 058/181] Adding the deploy application and triage files --- .../perform-static-analysis copy/action.yaml | 42 +++++++++++++++++++ .github/workflows/static-code-analysis.yaml | 34 +++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 .github/actions/perform-static-analysis copy/action.yaml create mode 100644 .github/workflows/static-code-analysis.yaml diff --git a/.github/actions/perform-static-analysis copy/action.yaml b/.github/actions/perform-static-analysis copy/action.yaml new file mode 100644 index 00000000..ec538dee --- /dev/null +++ b/.github/actions/perform-static-analysis copy/action.yaml @@ -0,0 +1,42 @@ +name: "Run SonarCloud static analysis" +description: "Perform SonarCloud static analysis" + +inputs: + sonar_organisation_key: + description: "Sonar organisation key, used to identify the project" + required: false + sonar_project_key: + description: "Sonar project key, used to identify the project" + required: false + sonar_token: + description: "Sonar token, the API key" + required: false + +runs: + using: "composite" + steps: + - name: "Download code coverage reports" + uses: actions/download-artifact@v4 + with: + path: coverage/ + pattern: coverage-*.xml + + - name: "Find coverage files" + id: coverage-files + shell: bash + run: | + FILES=$(find coverage -name 'coverage-*.xml' | paste -sd "," -) + echo "files=$FILES" >> $GITHUB_OUTPUT + + - name: "Perform SonarCloud static analysis" + uses: sonarsource/sonarqube-scan-action@v5.3.1 + env: + SONAR_TOKEN: ${{ inputs.sonar_token }} + with: + args: > + -Dsonar.organization=${{ inputs.sonar_organisation_key }} + -Dsonar.projectKey=${{ inputs.sonar_project_key }} + -Dsonar.branch.name=${{ github.ref_name }} + -Dsonar.python.coverage.reportPaths=${{ steps.coverage-files.outputs.files }} + -Dproject.settings=./scripts/config/sonar-scanner.properties + continue-on-error: true diff --git a/.github/workflows/static-code-analysis.yaml b/.github/workflows/static-code-analysis.yaml new file mode 100644 index 00000000..b56a2172 --- /dev/null +++ b/.github/workflows/static-code-analysis.yaml @@ -0,0 +1,34 @@ +name: "Perform SonarCloud static analysis" +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + environment: + description: "The deployment environment" + required: true + type: string + tag: + description: "The git tag to checkout or, if not passed in, the current branch" + required: false + type: string + +jobs: + static-analysis: + name: "Perform SonarCloud static analysis" + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: "Perform static analysis" + uses: ./.github/actions/perform-static-analysis + with: + sonar_organisation_key: ${{ vars.SONAR_ORGANISATION_KEY }} + sonar_project_key: ${{ vars.SONAR_PROJECT_KEY }} + sonar_token: ${{ secrets.SONAR_TOKEN }} From 532b8c58469a93dffa828dddfcfc1c676e33cd2d Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 09:25:06 +0000 Subject: [PATCH 059/181] Adding the deploy application and triage files --- .github/workflows/pipeline-deploy-application.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index a0faad76..d0ad2c34 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -15,11 +15,11 @@ on: inputs: tag: description: "Specify the tag to be used for deployment" - required: true + required: false type: string application_tag: description: "Specify the application tag to be used for deployment" - required: true + required: false type: string environment: description: "Deployment environment" @@ -67,6 +67,7 @@ jobs: build_type: ${{ matrix.build_type }} python_version: ${{ needs.metadata.outputs.python_version }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} + environment: ${{ needs.metadata.outputs.mgmt_environment }} repo_name: ${{ needs.metadata.outputs.reponame }} workspace: ${{ needs.metadata.outputs.workspace }} application_tag: ${{ inputs.application_tag || 'latest' }} From 7f5f26a649f039da2f79b0a275eaa335afcd5c45 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 09:27:39 +0000 Subject: [PATCH 060/181] Adding the deploy application and triage files --- tests/exploratory/PatientTriageExploratoryTest.jmx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/exploratory/PatientTriageExploratoryTest.jmx b/tests/exploratory/PatientTriageExploratoryTest.jmx index 7103b93d..7d6611f9 100644 --- a/tests/exploratory/PatientTriageExploratoryTest.jmx +++ b/tests/exploratory/PatientTriageExploratoryTest.jmx @@ -193,7 +193,7 @@ vars.put("CONFIG_PARTY", PARTIES[rand.nextInt(PARTIES.length)]); true previousAnswers = vars.get("ANSWERS"); if (previousAnswers == null || previousAnswers.equals("void")) { - previousAnswers = ""; + previousAnswers = ""; } previousAnswers = previousAnswers + ''', {"type": {"coding": [{"code": "%QUESTIONID%"}]},"valueCoding": {"code": "%ANSWERNUM%"}}''' @@ -201,7 +201,7 @@ previousAnswers = previousAnswers + ''', .replaceAll("%ANSWERNUM%", vars.get("answer")); if (previousAnswers[0] == ',') { - previousAnswers = previousAnswers.substring(1); + previousAnswers = previousAnswers.substring(1); } vars.put("ANSWERS", previousAnswers); @@ -238,7 +238,7 @@ vars.put("ANSWERS", previousAnswers); if (!prev.isResponseCodeOK()) { - vars.put("resources_ALL", ""); + vars.put("resources_ALL", ""); } groovy From 9f38269f5f499e2ea5abc5f536bc23dd3734f01d Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 11:46:00 +0000 Subject: [PATCH 061/181] Added extra information into the pre commit file --- .github/workflows/deploy-application-infrastructure.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/deploy-application-infrastructure.yaml b/.github/workflows/deploy-application-infrastructure.yaml index 17d94019..d7e4dacd 100644 --- a/.github/workflows/deploy-application-infrastructure.yaml +++ b/.github/workflows/deploy-application-infrastructure.yaml @@ -108,8 +108,6 @@ jobs: action: plan type: ${{ inputs.type }} workflow_timeout: ${{ inputs.workflow_timeout }} - download_appconfig_artifact: true - appconfig_artifact_name: "appconfig_feature_flags" secrets: inherit manual-approval-application-infra: @@ -147,8 +145,6 @@ jobs: action: apply type: ${{ inputs.type }} workflow_timeout: ${{ inputs.workflow_timeout }} - download_appconfig_artifact: true - appconfig_artifact_name: "appconfig_feature_flags" secrets: inherit deploy_summary: From 4cbacd11f5e22164d506105132f9f5a5da71bb81 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 11:56:00 +0000 Subject: [PATCH 062/181] Added extra information into the pre commit file --- .github/workflows/pipeline-deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index d0ad2c34..38177f60 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -43,7 +43,7 @@ jobs: uses: ./.github/workflows/quality-checks.yaml with: environment: ${{ needs.metadata.outputs.environment }} - workspace: ${{ needs.metadata.outputs.workspace }} + workspace: "default" stacks: "['triage']" type: app build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} From a110c8416e1c930dc43009a396483d163b12a75e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 12:06:36 +0000 Subject: [PATCH 063/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/variables.tf | 165 ++++------------------ 1 file changed, 27 insertions(+), 138 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index c2aeb498..f01dfb80 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -1,4 +1,20 @@ -# Dynamo Db vars +# Core vars +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + +variable "project_name" { + description = "Project name prefix" + type = string +} + +# DynamoDB vars variable "triage_table_name" { type = string } @@ -12,6 +28,7 @@ variable "hash_key" { description = "primary partition key" type = string } + variable "hash_key_type" { description = "partition key type" type = string @@ -23,39 +40,13 @@ variable "range_key" { type = string } -variable "aws_region" { - description = "sort key" - type = string -} - -variable "environment" { - description = "sort key" - type = string -} - -variable "project_name" { - description = "sort key" - type = string -} - # IAM vars -data "aws_iam_policy_document" "iam_policy" { - statement { - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["lambda.amazonaws.com"] - } - } -} - variable "name" { type = string } variable "assume_services" { - description = "AWS Sewrrvices a;;pwed to assume this role" + description = "AWS services allowed to assume this role" type = list(string) } @@ -65,30 +56,15 @@ variable "policy_arns" { } variable "bucket_name" { - description = "s3 bucket we are accessing" + description = "S3 bucket name" type = string } variable "table_arns" { - description = "Dynamodb arns" + description = "DynamoDB table ARNs" type = list(string) } -variable "aws_region" { - description = "sort key" - type = string -} - -variable "environment" { - description = "sort key" - type = string -} - -variable "project_name" { - description = "sort key" - type = string -} - # Lambda vars variable "function_name" { type = string @@ -102,10 +78,6 @@ variable "runtime" { type = string } -variable "bucket_name" { - type = string -} - variable "s3_key" { type = string } @@ -118,7 +90,6 @@ variable "mem_size" { type = number } -# Optional environment variables for DynamoDB tables variable "body_map_table" { type = string default = null @@ -134,34 +105,21 @@ variable "triage_node_table" { default = null } -variable "project_name" { - type = string - default = null -} - -variable "aws_region" { - type = string - default = null -} - -variable "environment" { - type = string - default = null +variable "policy_jsons" { + description = "List of JSON policies for Lambda" + type = list(string) + default = [] } # REST API vars -variable "name" { - type = string -} - variable "resources" { type = list(object({ path_part = string method = string lambda_invoke_arn = string })) - } + variable "authorization" { type = string } @@ -176,51 +134,16 @@ variable "int_http_method" { default = "POST" } -variable "aws_region" { - description = "sort key" - type = string -} - -variable "environment" { - description = "sort key" - type = string -} - -variable "project_name" { - description = "sort key" - type = string -} - variable "stage_name" { type = string } -# s3 vars -variable "bucket_name" { - description = "Name of the S3 bucket" - type = string -} - +# S3 vars variable "region" { description = "AWS region for S3 bucket" type = string } -variable "aws_region" { - description = "sort key" - type = string -} - -variable "environment" { - description = "sort key" - type = string -} - -variable "project_name" { - description = "sort key" - type = string -} - # Trigger vars variable "statement_id" { type = string @@ -238,11 +161,6 @@ variable "principal" { type = string } -variable "bucket_name" { - type = string - default = null -} - variable "bucket_arn" { type = string default = null @@ -266,32 +184,3 @@ variable "api_gateway_source_arn" { type = string default = null } - -variable "aws_region" { - description = "sort key" - type = string -} - -variable "environment" { - description = "sort key" - type = string -} - -variable "project_name" { - description = "sort key" - type = string -} - -variable "policy_jsons" { - description = "List of JSON policies for Lambda" - default = jsonencode({ - Version = "2012-10-17", - Statement = [{ - Effect = "Allow", - Principal = { - Service = var.assume_services - }, - Action = "sts:AssumeRole" - }] - }) -} From 7998629eb019017ba240ce4c603349b307c73ad8 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 12:13:18 +0000 Subject: [PATCH 064/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/variables.tf | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index f01dfb80..ca947644 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -1,14 +1,4 @@ # Core vars -variable "aws_region" { - description = "AWS region" - type = string -} - -variable "environment" { - description = "Deployment environment" - type = string -} - variable "project_name" { description = "Project name prefix" type = string From a2c63c3dea969018769bdb5edde1b4c9827c24ea Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 12:43:44 +0000 Subject: [PATCH 065/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/lambda.tf | 19 +++++++++++-------- infrastructure/stacks/triage/variables.tf | 10 ++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 34842d55..d8aba825 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -9,15 +9,18 @@ module "s3lambda" { runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size - body_map_table = module.bodymap.table_name - starting_node_table = module.startingnode.table_name - triage_node_table = module.triagenode.table_name - environment_variables = { - "ENVIRONMENT" = var.environment - "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace - "PROJECT_NAME" = var.project - } + environment_variables = merge( + { + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "PROJECT_NAME" = var.project_name + }, + var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, + var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, + var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, + var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} + ) } diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index ca947644..f01dfb80 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -1,4 +1,14 @@ # Core vars +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + variable "project_name" { description = "Project name prefix" type = string From 0492f4e32345df7518d3cf2adc5928bfd0b473bd Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 3 Feb 2026 15:06:33 +0000 Subject: [PATCH 066/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/variables.tf | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index f01dfb80..0368621d 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -1,14 +1,3 @@ -# Core vars -variable "aws_region" { - description = "AWS region" - type = string -} - -variable "environment" { - description = "Deployment environment" - type = string -} - variable "project_name" { description = "Project name prefix" type = string From 46555678c1347f8fcb90831b6b6c1882100ca722 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 4 Feb 2026 10:56:10 +0000 Subject: [PATCH 067/181] Fix terraform directory paths for stack deployments --- infrastructure/.gitignore | 9 --------- infrastructure/environments/dev/triage.tfvars | 0 infrastructure/stacks/triage/dynamodb.tf | 4 ++-- infrastructure/stacks/triage/lambda.tf | 18 +++++++++--------- infrastructure/triage.tfvars | 11 +++++++++++ scripts/terraform/terraform.mk | 6 ++++++ 6 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 infrastructure/environments/dev/triage.tfvars create mode 100644 infrastructure/triage.tfvars diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore index ac09308b..7a79078c 100644 --- a/infrastructure/.gitignore +++ b/infrastructure/.gitignore @@ -15,17 +15,8 @@ crash.*.log # password, private keys, and other secrets. These should not be part of version # control as they are data points which are potentially sensitive and subject # to change depending on the environment. -*.tfvars *.tfvars.json -# Exception: Allow specific tfvars files to be committed -!common.tfvars -!environment.tfvars -!github_runner.tfvars -!account_security.tfvars -!terraform_management.tfvars -!account_wide.tfvars - # Ignore override files as they are usually used to override resources locally and so # are not checked in override.tf diff --git a/infrastructure/environments/dev/triage.tfvars b/infrastructure/environments/dev/triage.tfvars new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index e701c9b8..9f44728b 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -5,8 +5,8 @@ module "startingnode" { range_key = "GenderAgeParty" attributes = [ - { hash_key = "Skillset", type = "S" }, - { hash_key = "Skillset", type = "S" } + { name = "Skillset", type = "S" }, + { name = "GenderAgeParty", type = "S" } ] } diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index d8aba825..f86b1aeb 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -1,14 +1,14 @@ module "s3lambda" { - source = "../../modules/lambda" - aws_region = var.aws_region - function_name = "${var.project_name}-${var.environment}s3-Lambda" - policy_jsons = var.policy_jsons - handler = "s3_configurator.handler" - s3_bucket_name = module.SAETBucket.bucket_name - runtime = var.runtime - s3_key = var.s3_key - memory_size = var.mem_size + source = "../../modules/lambda" + aws_region = var.aws_region + function_name = "${var.project_name}-${var.environment}s3-Lambda" + policy_jsons = var.policy_jsons + handler = "s3_configurator.handler" + s3_bucket_name = module.SAETBucket.bucket_name + runtime = var.runtime + s3_key = var.s3_key + memory_size = var.mem_size environment_variables = merge( diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars new file mode 100644 index 00000000..28c6f84d --- /dev/null +++ b/infrastructure/triage.tfvars @@ -0,0 +1,11 @@ +aws_region = "eu-west-2" +environment = "test" +project_name = "saet" + +#Lambda +mem_size = 512 +runtime = "python3.13" +s3_key = "lambda_function.zip" + +#Rest API +stage_name = "beta" diff --git a/scripts/terraform/terraform.mk b/scripts/terraform/terraform.mk index c1112c91..15deb647 100644 --- a/scripts/terraform/terraform.mk +++ b/scripts/terraform/terraform.mk @@ -7,6 +7,12 @@ TF_ENV ?= dev STACK ?= ${stack} TERRAFORM_STACK ?= $(or ${STACK}, infrastructure/environments/${TF_ENV}) +# If STACK is set and doesn't contain a path separator, assume it's a stack name under infrastructure/stacks/ +ifneq ($(findstring /,$(STACK)),/) + ifneq ($(STACK),) + TERRAFORM_STACK := infrastructure/stacks/${STACK} + endif +endif dir ?= ${TERRAFORM_STACK} terraform-init: # Initialise Terraform - optional: terraform_dir|dir=[path to a directory where the command will be executed, relative to the project's top-level directory, default is one of the module variables or the example directory, if not set], terraform_opts|opts=[options to pass to the Terraform init command, default is none/empty] @Development From 7a33f73b2ee36ad10c35b2c8fa3c54027ac53d93 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 4 Feb 2026 11:23:25 +0000 Subject: [PATCH 068/181] Added extra information into the pre commit file --- scripts/terraform/terraform.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/terraform/terraform.mk b/scripts/terraform/terraform.mk index 15deb647..7e245acf 100644 --- a/scripts/terraform/terraform.mk +++ b/scripts/terraform/terraform.mk @@ -9,9 +9,9 @@ STACK ?= ${stack} TERRAFORM_STACK ?= $(or ${STACK}, infrastructure/environments/${TF_ENV}) # If STACK is set and doesn't contain a path separator, assume it's a stack name under infrastructure/stacks/ ifneq ($(findstring /,$(STACK)),/) - ifneq ($(STACK),) - TERRAFORM_STACK := infrastructure/stacks/${STACK} - endif + ifneq ($(STACK),) + TERRAFORM_STACK := infrastructure/stacks/${STACK} + endif endif dir ?= ${TERRAFORM_STACK} From 8504ee848a93da43bdfaa827c511b14c42ebfe67 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 12:43:03 +0000 Subject: [PATCH 069/181] Initial application deployment --- infrastructure/modules/dynamodb/main.tf | 23 +--- infrastructure/stacks/triage/dynamodb.tf | 12 +- infrastructure/stacks/triage/iam.tf | 136 +++++++++++----------- infrastructure/stacks/triage/lambda.tf | 59 ++++++---- infrastructure/stacks/triage/restapi.tf | 138 +++++++++++------------ infrastructure/stacks/triage/s3.tf | 10 +- infrastructure/stacks/triage/trigger.tf | 52 ++++----- 7 files changed, 211 insertions(+), 219 deletions(-) diff --git a/infrastructure/modules/dynamodb/main.tf b/infrastructure/modules/dynamodb/main.tf index 3fe7594f..46fc1295 100755 --- a/infrastructure/modules/dynamodb/main.tf +++ b/infrastructure/modules/dynamodb/main.tf @@ -1,25 +1,6 @@ -# resource "aws_dynamodb_table" "table" { -# name = var.table_name -# billing_mode = var.billing -# hash_key = var.hash_key -# range_key = var.range_key - -# attribute { -# name = var.hash_key -# type = var.hash_key_type -# } - -# dynamic "attribute" { -# for_each = var.range_key != null ? [var.range_key] : [] -# content { -# name = var.range_key -# type = var.hash_key_type -# } -# } -# } module "dynamodb_table" { - # Module version: 4.3.0 - source = "git::https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git?ref=1ab93ca82023b72fe37de7f17cc10714867b2d4f" + # Module version: 5.5.0 + source = "git::https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git?ref=45c9cb10c2f6209e7362bba92cadd5ab3c9e2003" name = var.table_name hash_key = var.hash_key diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index 9f44728b..34fc32b7 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -1,6 +1,6 @@ -module "startingnode" { +module "starting_coords" { source = "../../modules/dynamodb" - table_name = "${var.project_name}-${var.environment}-Starting-Node" + table_name = "${local.resource_prefix}-StartingCoords" hash_key = "Skillset" range_key = "GenderAgeParty" @@ -10,9 +10,9 @@ module "startingnode" { ] } -module "triagenode" { +module "triage_nodes" { source = "../../modules/dynamodb" - table_name = "${var.project_name}-${var.environment}-Triage-Node" + table_name = "${local.resource_prefix}-TriageNodes" hash_key = "Coordinate" attributes = [{ @@ -21,9 +21,9 @@ module "triagenode" { }] } -module "bodymap" { +module "bodymaps" { source = "../../modules/dynamodb" - table_name = "${var.project_name}-${var.environment}-Body-Map-Node" + table_name = "${local.resource_prefix}-BodyMaps" hash_key = "id" attributes = [{ diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index b1de91c1..36810ed7 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -1,75 +1,75 @@ -resource "aws_iam_role" "lambda_role" { - assume_role_policy = jsonencode({ - Version = "2012-10-17", - Statement = [{ - Effect = "Allow", - Principal = { - Service = var.assume_services - }, - Action = "sts:AssumeRole" - }] - }) - name = var.name -} +# resource "aws_iam_role" "lambda_role" { +# assume_role_policy = jsonencode({ +# Version = "2012-10-17", +# Statement = [{ +# Effect = "Allow", +# Principal = { +# Service = var.assume_services +# }, +# Action = "sts:AssumeRole" +# }] +# }) +# name = var.name +# } -resource "aws_iam_policy" "s3_access" { - name = "${var.name}-s3-access" - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Effect = "Allow", - Action = [ - "s3:GetObject", - "s3:PutObject", - "s3:ListBucket" - ], - Resource = [ - # bucket itself - "arn:aws:s3:::${var.bucket_name}", - # all objects inside - "arn:aws:s3:::${var.bucket_name}/*" - ] - }] - }) -} +# resource "aws_iam_policy" "s3_access" { +# name = "${var.name}-s3-access" +# policy = jsonencode({ +# Version = "2012-10-17", +# Statement = [ +# { +# Effect = "Allow", +# Action = [ +# "s3:GetObject", +# "s3:PutObject", +# "s3:ListBucket" +# ], +# Resource = [ +# # bucket itself +# "arn:aws:s3:::${var.bucket_name}", +# # all objects inside +# "arn:aws:s3:::${var.bucket_name}/*" +# ] +# }] +# }) +# } -resource "aws_iam_policy" "ddb_access" { - name = "${var.name}-dynamo-db-access" - policy = jsonencode({ - Version = "2012-10-17", - Statement = [ - { - Effect = "Allow", - Action = [ - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:BatchWriteItem", - "dynamodb:Query", - "dynamodb:Scan" - ], - Resource = concat( - var.table_arns, - [for q in var.table_arns : "${q}/index/*"] - ) - }] - }) -} +# resource "aws_iam_policy" "ddb_access" { +# name = "${var.name}-dynamo-db-access" +# policy = jsonencode({ +# Version = "2012-10-17", +# Statement = [ +# { +# Effect = "Allow", +# Action = [ +# "dynamodb:GetItem", +# "dynamodb:BatchGetItem", +# "dynamodb:BatchWriteItem", +# "dynamodb:Query", +# "dynamodb:Scan" +# ], +# Resource = concat( +# var.table_arns, +# [for q in var.table_arns : "${q}/index/*"] +# ) +# }] +# }) +# } -resource "aws_iam_role_policy_attachment" "s3_attach" { - role = aws_iam_role.lambda_role.name - policy_arn = aws_iam_policy.s3_access.arn -} +# resource "aws_iam_role_policy_attachment" "s3_attach" { +# role = aws_iam_role.lambda_role.name +# policy_arn = aws_iam_policy.s3_access.arn +# } -resource "aws_iam_role_policy_attachment" "ddb_aatach" { - role = aws_iam_role.lambda_role.name - policy_arn = aws_iam_policy.ddb_access.arn -} +# resource "aws_iam_role_policy_attachment" "ddb_aatach" { +# role = aws_iam_role.lambda_role.name +# policy_arn = aws_iam_policy.ddb_access.arn +# } -resource "aws_iam_role_policy_attachment" "attachments" { - for_each = toset(var.policy_arns) +# resource "aws_iam_role_policy_attachment" "attachments" { +# for_each = toset(var.policy_arns) - role = aws_iam_role.lambda_role.name - policy_arn = each.value -} +# role = aws_iam_role.lambda_role.name +# policy_arn = each.value +# } diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index f86b1aeb..2452f004 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -1,26 +1,41 @@ -module "s3lambda" { - source = "../../modules/lambda" - aws_region = var.aws_region - function_name = "${var.project_name}-${var.environment}s3-Lambda" - policy_jsons = var.policy_jsons - handler = "s3_configurator.handler" - s3_bucket_name = module.SAETBucket.bucket_name - runtime = var.runtime - s3_key = var.s3_key - memory_size = var.mem_size +# module "s3lambda" { +# source = "../../modules/lambda" +# aws_region = var.aws_region +# function_name = "${var.project_name}-${var.environment}s3-Lambda" +# policy_jsons = var.policy_jsons +# handler = "s3_configurator.handler" +# s3_bucket_name = module.SAETBucket.bucket_name +# runtime = var.runtime +# s3_key = var.s3_key +# memory_size = var.mem_size - environment_variables = merge( - { - "ENVIRONMENT" = var.environment - "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace - "PROJECT_NAME" = var.project_name - }, - var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, - var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, - var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, - var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} - ) +# environment_variables = merge( +# { +# "ENVIRONMENT" = var.environment +# "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace +# "PROJECT_NAME" = var.project_name +# }, +# var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, +# var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, +# var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, +# var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} +# ) -} +# } + +# module "apiglambda" { +# source = "../../modules/lambda" +# aws_region = var.aws_region +# function_name = "APIG-lambda-JACK-TEST" +# lambda_role = module.iam.lambda_role_arn +# handler = "api_gateway_configurator.handler" +# bucket_name = module.SAETBucket.bucket_name +# runtime = var.runtime +# s3_key = var.s3_key +# mem_size = var.mem_size +# body_map_table = module.bodymap.table_name +# starting_node_table = module.startingnode.table_name +# triage_node_table = module.triagenode.table_name +# } diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 9bbf595b..6f76ace6 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,79 +1,79 @@ -resource "aws_api_gateway_rest_api" "triage" { - name = var.name -} +# resource "aws_api_gateway_rest_api" "triage" { +# name = var.name +# } -resource "aws_api_gateway_resource" "resource" { - for_each = { for w in var.resources : w.path_part => w } - rest_api_id = aws_api_gateway_rest_api.triage.id - parent_id = aws_api_gateway_rest_api.triage.root_resource_id - path_part = each.value.path_part -} +# resource "aws_api_gateway_resource" "resource" { +# for_each = { for w in var.resources : w.path_part => w } +# rest_api_id = aws_api_gateway_rest_api.triage.id +# parent_id = aws_api_gateway_rest_api.triage.root_resource_id +# path_part = each.value.path_part +# } -resource "aws_api_gateway_method" "methods" { - for_each = { for w in var.resources : w.path_part => w } - rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = each.value.method - authorization = var.authorization -} +# resource "aws_api_gateway_method" "methods" { +# for_each = { for w in var.resources : w.path_part => w } +# rest_api_id = aws_api_gateway_rest_api.triage.id +# resource_id = aws_api_gateway_resource.resource[each.key].id +# http_method = each.value.method +# authorization = var.authorization +# } -# Create method responses for the API methods -resource "aws_api_gateway_method_response" "method_response" { - for_each = { for w in var.resources : w.path_part => w } - rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method - status_code = "200" -} +# # Create method responses for the API methods +# resource "aws_api_gateway_method_response" "method_response" { +# for_each = { for w in var.resources : w.path_part => w } +# rest_api_id = aws_api_gateway_rest_api.triage.id +# resource_id = aws_api_gateway_resource.resource[each.key].id +# http_method = aws_api_gateway_method.methods[each.key].http_method +# status_code = "200" +# } -resource "aws_api_gateway_integration" "integrations" { - for_each = { for w in var.resources : w.path_part => w } - rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method - integration_http_method = var.int_http_method - type = var.type - uri = each.value.lambda_invoke_arn -} +# resource "aws_api_gateway_integration" "integrations" { +# for_each = { for w in var.resources : w.path_part => w } +# rest_api_id = aws_api_gateway_rest_api.triage.id +# resource_id = aws_api_gateway_resource.resource[each.key].id +# http_method = aws_api_gateway_method.methods[each.key].http_method +# integration_http_method = var.int_http_method +# type = var.type +# uri = each.value.lambda_invoke_arn +# } -# Create integration responses for the API methods -resource "aws_api_gateway_integration_response" "integration_response" { - for_each = { for w in var.resources : w.path_part => w } - rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method - status_code = aws_api_gateway_method_response.method_response[each.key].status_code - selection_pattern = "" -} +# # Create integration responses for the API methods +# resource "aws_api_gateway_integration_response" "integration_response" { +# for_each = { for w in var.resources : w.path_part => w } +# rest_api_id = aws_api_gateway_rest_api.triage.id +# resource_id = aws_api_gateway_resource.resource[each.key].id +# http_method = aws_api_gateway_method.methods[each.key].http_method +# status_code = aws_api_gateway_method_response.method_response[each.key].status_code +# selection_pattern = "" +# } -# Create a deployment to publish the API -resource "aws_api_gateway_deployment" "deployment" { - rest_api_id = aws_api_gateway_rest_api.triage.id +# # Create a deployment to publish the API +# resource "aws_api_gateway_deployment" "deployment" { +# rest_api_id = aws_api_gateway_rest_api.triage.id - depends_on = [ - aws_api_gateway_integration.integrations, - aws_api_gateway_integration_response.integration_response, - aws_api_gateway_method.methods - ] +# depends_on = [ +# aws_api_gateway_integration.integrations, +# aws_api_gateway_integration_response.integration_response, +# aws_api_gateway_method.methods +# ] - # Force redeployment when any API resources change - triggers = { - redeployment = sha1(jsonencode({ - resources = aws_api_gateway_resource.resource - methods = aws_api_gateway_method.methods - integrations = aws_api_gateway_integration.integrations - integration_responses = aws_api_gateway_integration_response.integration_response - })) - } +# # Force redeployment when any API resources change +# triggers = { +# redeployment = sha1(jsonencode({ +# resources = aws_api_gateway_resource.resource +# methods = aws_api_gateway_method.methods +# integrations = aws_api_gateway_integration.integrations +# integration_responses = aws_api_gateway_integration_response.integration_response +# })) +# } - lifecycle { - create_before_destroy = true - } -} +# lifecycle { +# create_before_destroy = true +# } +# } -# Create a stage (environment) for the API -resource "aws_api_gateway_stage" "stage" { - deployment_id = aws_api_gateway_deployment.deployment.id - rest_api_id = aws_api_gateway_rest_api.triage.id - stage_name = var.stage_name -} +# # Create a stage (environment) for the API +# resource "aws_api_gateway_stage" "stage" { +# deployment_id = aws_api_gateway_deployment.deployment.id +# rest_api_id = aws_api_gateway_rest_api.triage.id +# stage_name = var.stage_name +# } diff --git a/infrastructure/stacks/triage/s3.tf b/infrastructure/stacks/triage/s3.tf index 8e002d2d..24968d57 100644 --- a/infrastructure/stacks/triage/s3.tf +++ b/infrastructure/stacks/triage/s3.tf @@ -1,8 +1,4 @@ -resource "aws_s3_bucket" "storage" { - bucket = var.bucket_name - region = var.region - - lifecycle { - prevent_destroy = true - } +module "pathway_artifact_bucket" { + source = "../../modules/s3" + bucket_name = "${local.resource_prefix}-artifact" } diff --git a/infrastructure/stacks/triage/trigger.tf b/infrastructure/stacks/triage/trigger.tf index bc82a7cd..1cc36033 100644 --- a/infrastructure/stacks/triage/trigger.tf +++ b/infrastructure/stacks/triage/trigger.tf @@ -1,28 +1,28 @@ -resource "aws_lambda_permission" "allows3" { - count = var.bucket_arn != null ? 1 : 0 - statement_id = var.statement_id - action = var.action - function_name = var.lambda_name - principal = var.principal - source_arn = var.bucket_arn -} +# resource "aws_lambda_permission" "allows3" { +# count = var.bucket_arn != null ? 1 : 0 +# statement_id = var.statement_id +# action = var.action +# function_name = var.lambda_name +# principal = var.principal +# source_arn = var.bucket_arn +# } -resource "aws_s3_bucket_notification" "bucket_notification" { - count = var.bucket_name != null ? 1 : 0 - bucket = var.bucket_name - lambda_function { - lambda_function_arn = var.lambda_arn - events = var.events - #filter_prefix = var.filter_prefix - } - depends_on = [aws_lambda_permission.allows3] -} +# resource "aws_s3_bucket_notification" "bucket_notification" { +# count = var.bucket_name != null ? 1 : 0 +# bucket = var.bucket_name +# lambda_function { +# lambda_function_arn = var.lambda_arn +# events = var.events +# #filter_prefix = var.filter_prefix +# } +# depends_on = [aws_lambda_permission.allows3] +# } -resource "aws_lambda_permission" "allowapig" { - count = var.api_gateway_source_arn != null ? 1 : 0 - statement_id = var.statement_id - action = var.action - function_name = var.lambda_name - principal = var.principal - source_arn = var.api_gateway_source_arn -} +# resource "aws_lambda_permission" "allowapig" { +# count = var.api_gateway_source_arn != null ? 1 : 0 +# statement_id = var.statement_id +# action = var.action +# function_name = var.lambda_name +# principal = var.principal +# source_arn = var.api_gateway_source_arn +# } From caa2291cf0209c69dd045232d35e6e9e9bec0d8d Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 12:49:58 +0000 Subject: [PATCH 070/181] Initial application deployment --- infrastructure/local/versions.tf | 2 +- infrastructure/modules/lambda/main.tf | 20 -------------------- infrastructure/modules/shield/terraform.tf | 2 +- infrastructure/remote/versions.tf | 2 +- 4 files changed, 3 insertions(+), 23 deletions(-) diff --git a/infrastructure/local/versions.tf b/infrastructure/local/versions.tf index 06feaff2..292c49c0 100644 --- a/infrastructure/local/versions.tf +++ b/infrastructure/local/versions.tf @@ -7,7 +7,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "6.5.0" + version = "6.31.0" } } } diff --git a/infrastructure/modules/lambda/main.tf b/infrastructure/modules/lambda/main.tf index e1422361..e49b7e39 100644 --- a/infrastructure/modules/lambda/main.tf +++ b/infrastructure/modules/lambda/main.tf @@ -35,23 +35,3 @@ module "lambda" { cloudwatch_logs_retention_in_days = var.cloudwatch_logs_retention logging_system_log_level = var.cloudwatch_log_level } - - - -resource "aws_lambda_function" "triage" { - function_name = var.function_name - role = var.lambda_role - handler = var.handler - runtime = var.runtime - s3_bucket = var.bucket_name - s3_key = var.s3_key - memory_size = var.mem_size - environment { - variables = merge( - var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, - var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, - var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, - var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} - ) - } -} diff --git a/infrastructure/modules/shield/terraform.tf b/infrastructure/modules/shield/terraform.tf index e895794a..175ffbfc 100644 --- a/infrastructure/modules/shield/terraform.tf +++ b/infrastructure/modules/shield/terraform.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "6.5.0" + version = "6.31.0" } } } diff --git a/infrastructure/remote/versions.tf b/infrastructure/remote/versions.tf index 53ce97ec..5dcccbf9 100644 --- a/infrastructure/remote/versions.tf +++ b/infrastructure/remote/versions.tf @@ -7,7 +7,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "6.5.0" + version = "6.31.0" } } } From ae4c3cfad080261ef903c0ffd382e4d3e24b9c19 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 13:02:53 +0000 Subject: [PATCH 071/181] Initial application deployment --- .github/workflows/pipeline-deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 38177f60..14093307 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -67,7 +67,7 @@ jobs: build_type: ${{ matrix.build_type }} python_version: ${{ needs.metadata.outputs.python_version }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.mgmt_environment }} + environment: ${{ needs.metadata.outputs.environment }} repo_name: ${{ needs.metadata.outputs.reponame }} workspace: ${{ needs.metadata.outputs.workspace }} application_tag: ${{ inputs.application_tag || 'latest' }} From f1962402541d0fbc7606368454525f83bcaf06a4 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 14:28:13 +0000 Subject: [PATCH 072/181] Initial application deployment --- .github/workflows/build-project.yaml | 72 ---------------------------- 1 file changed, 72 deletions(-) diff --git a/.github/workflows/build-project.yaml b/.github/workflows/build-project.yaml index 363ed522..546b49f7 100644 --- a/.github/workflows/build-project.yaml +++ b/.github/workflows/build-project.yaml @@ -56,78 +56,6 @@ jobs: environment: ${{ inputs.environment }} steps: - - name: "Checkout code" - uses: actions/checkout@v6 - with: - ref: ${{ inputs.application_tag && inputs.application_tag != 'latest' && inputs.application_tag || '' }} - - - name: "Install asdf" - uses: asdf-vm/actions/setup@v4.0.1 - with: - asdf_branch: v0.15.0 - - - name: "Cache asdf tools" - id: asdf-cache - uses: actions/cache@v5 - with: - path: ~/.asdf - key: asdf-${{ runner.os }}-${{ hashFiles('.tool-versions') }} - - - name: "Install tools from .tool-versions" - if: steps.asdf-cache.outputs.cache-hit != 'true' - uses: asdf-vm/actions/install@v4.0.1 - - - name: "Configure AWS Credentials" - uses: ./.github/actions/configure-credentials - with: - aws_account_id: ${{ secrets.ACCOUNT_ID }} - aws_region: ${{ vars.AWS_REGION }} - type: ${{ inputs.type }} - environment: ${{ inputs.environment }} - - - name: "Get directories" - id: get-directories - run: | - if [[ "${{ inputs.build_type }}" == "service" ]]; then - echo "working_directory=services/${{ inputs.name }}" >> $GITHUB_OUTPUT - echo "build_directory=build/services/${{ inputs.name }}" >> $GITHUB_OUTPUT - elif [[ "${{ inputs.build_type }}" == "package" ]]; then - echo "working_directory=application/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT - echo "build_directory=build/packages/${{ inputs.name }}" >> $GITHUB_OUTPUT - else - echo "Invalid build type: ${{ inputs.build_type }}" - exit 1 - fi - - - name: "Install project" - run: make install - working-directory: ${{ steps.get-directories.outputs.working_directory }} - - - name: "Run linting" - run: make lint - working-directory: ${{ steps.get-directories.outputs.working_directory }} - - - name: "Run unit tests" - run: make unit-test - working-directory: ${{ steps.get-directories.outputs.working_directory }} - - - name: "Publish coverage report to GitHub" - uses: actions/upload-artifact@v6 - with: - name: coverage-${{ inputs.name }}.xml - path: ${{ steps.get-directories.outputs.working_directory }}/coverage-${{ inputs.name }}.xml - if-no-files-found: ignore - - - name: "Build project" - run: make build - working-directory: ${{ steps.get-directories.outputs.working_directory }} - env: - SERVICE: ${{ inputs.name }} - PACKAGE: ${{ inputs.name }} - PYTHON_VERSION: ${{ inputs.python_version }} - APPLICATION_TAG: ${{ inputs.application_tag }} - REPO_NAME: ${{ inputs.repo_name }} - - name: "Publish artefacts to S3" run: make publish working-directory: ${{ steps.get-directories.outputs.working_directory }} From 9c9dbaf2491c7b42f2d9abc818da6ed99876b6ec Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 14:29:53 +0000 Subject: [PATCH 073/181] Initial application deployment --- .github/workflows/pipeline-deploy-application.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 14093307..81857246 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -59,8 +59,6 @@ jobs: include: - name: "python" build_type: "package" - - name: "triage" - build_type: "service" uses: ./.github/workflows/build-project.yaml with: name: ${{ matrix.name }} From 8e112cc45f9ad4889757ba9be7efbd3770da2e8a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 14:45:18 +0000 Subject: [PATCH 074/181] Initial application deployment --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bb631eeb..03e44749 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,11 @@ build: # Build the project artefact @Pipeline popd publish: # Publish the project artefact @Pipeline - # TODO: Implement the artefact publishing step + @echo "Publishing lambda_function.zip to S3 artifact bucket..." + @BUCKET_NAME="$${REPO_NAME}-$${ENVIRONMENT}-triage-artifact"; \ + echo "Target S3 bucket: $$BUCKET_NAME"; \ + aws s3 cp src/lambda_function.zip s3://$$BUCKET_NAME/lambda_function.zip + @echo "Successfully published lambda_function.zip" deploy-local: build # Deploy the project artefact to the target environment @Pipeline echo "Deploying to localstack" From 9dbd485a800b814d3ca363fdb0b72a789ae46df2 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 5 Feb 2026 14:50:49 +0000 Subject: [PATCH 075/181] Initial application deployment --- .github/workflows/build-project.yaml | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-project.yaml b/.github/workflows/build-project.yaml index 546b49f7..396f5666 100644 --- a/.github/workflows/build-project.yaml +++ b/.github/workflows/build-project.yaml @@ -56,9 +56,36 @@ jobs: environment: ${{ inputs.environment }} steps: + - name: "Checkout code" + uses: actions/checkout@v6 + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Set up Python" + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: "Build project" + run: make build + env: + SERVICE: ${{ inputs.name }} + PACKAGE: ${{ inputs.name }} + COMMIT_HASH: ${{ inputs.commit_hash }} + ENVIRONMENT: ${{ inputs.environment }} + REPO_NAME: ${{ inputs.repo_name }} + WORKSPACE: ${{ inputs.workspace }} + APPLICATION_TAG: ${{ inputs.application_tag }} + RELEASE_BUILD: ${{ inputs.release_build }} + - name: "Publish artefacts to S3" run: make publish - working-directory: ${{ steps.get-directories.outputs.working_directory }} env: SERVICE: ${{ inputs.name }} PACKAGE: ${{ inputs.name }} @@ -73,5 +100,5 @@ jobs: uses: actions/upload-artifact@v6 with: name: ${{ inputs.name }}-${{ inputs.build_type }}-artefacts - path: ${{ steps.get-directories.outputs.build_directory }} + path: src/lambda_function.zip if-no-files-found: error From 2f975ae34867ad270108b634b74560e2f66d773e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 6 Feb 2026 12:42:29 +0000 Subject: [PATCH 076/181] Adding lambda --- infrastructure/stacks/triage/lambda.tf | 41 +++++++++++++------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 2452f004..1391e26f 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -1,27 +1,26 @@ -# module "s3lambda" { -# source = "../../modules/lambda" -# aws_region = var.aws_region -# function_name = "${var.project_name}-${var.environment}s3-Lambda" -# policy_jsons = var.policy_jsons -# handler = "s3_configurator.handler" -# s3_bucket_name = module.SAETBucket.bucket_name -# runtime = var.runtime -# s3_key = var.s3_key -# memory_size = var.mem_size +module "s3lambda" { + source = "../../modules/lambda" + function_name = "${var.project_name}-${var.environment}s3-Lambda" + policy_jsons = var.policy_jsons + handler = "s3_configurator.handler" + s3_bucket_name = module.SAETBucket.bucket_name + runtime = var.runtime + s3_key = var.s3_key + memory_size = var.mem_size -# environment_variables = merge( -# { -# "ENVIRONMENT" = var.environment -# "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace -# "PROJECT_NAME" = var.project_name -# }, -# var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, -# var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, -# var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, -# var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} -# ) + environment_variables = merge( + { + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "PROJECT_NAME" = var.project_name + }, + var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, + var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, + var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, + var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} + ) # } From a1c483febcdb2f1a52751ec529c00355fed07d48 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:43:56 +0000 Subject: [PATCH 077/181] fix: SAET-0000 test pre-commit --- infrastructure/stacks/account_wide/data.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/account_wide/data.tf b/infrastructure/stacks/account_wide/data.tf index c6dc158e..ddc9fc48 100644 --- a/infrastructure/stacks/account_wide/data.tf +++ b/infrastructure/stacks/account_wide/data.tf @@ -9,3 +9,4 @@ data "aws_subnet" "vpc_private_subnets_by_count" { count = length(module.vpc.private_subnets) id = module.vpc.private_subnets[count.index] } + From 5449fe2ef9bc3b2faf798b486743f98abb498177 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:11:17 +0000 Subject: [PATCH 078/181] feat: SAET-0000 Add triage to the application pipeline --- .../deploy-application-infrastructure.yaml | 32 +-------- .../pipeline-deploy-application.yaml | 72 +++++++------------ scripts/githooks/check-english-usage.sh | 10 ++- 3 files changed, 34 insertions(+), 80 deletions(-) diff --git a/.github/workflows/deploy-application-infrastructure.yaml b/.github/workflows/deploy-application-infrastructure.yaml index d7e4dacd..9982acc1 100644 --- a/.github/workflows/deploy-application-infrastructure.yaml +++ b/.github/workflows/deploy-application-infrastructure.yaml @@ -49,7 +49,7 @@ on: stacks: description: "A list of the infrastructure stacks to deploy from the domain. If not supplied, no infrastructure will be deployed" required: false - default: "['database', 'crud_apis', 'data_migration', 'read_only_viewer', 'opensearch', 'etl_ods', 'dos_search', 'is_performance', 'ui', 'app_config']" + default: "['triage']" type: string outputs: plan_result: @@ -61,41 +61,11 @@ on: jobs: - # Generate AppConfig artifacts (feature flags JSON, ensure tfvars) and publish for downstream jobs - prepare-appconfig: - name: "Prepare AppConfig artifacts" - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v6 - with: - ref: ${{ inputs.tag }} - - - name: "Install Python dependencies" - run: | - python -m pip install --upgrade pip - pip install pyyaml - - - name: "Generate feature flags JSON" - env: - ENVIRONMENT: ${{ inputs.workspace == 'default' && inputs.environment || 'workspace' }} - run: | - /bin/bash ./scripts/workflow/generate-feature-flags.sh - - - name: "Upload AppConfig feature flags" - uses: actions/upload-artifact@v6 - with: - name: appconfig_feature_flags_${{ inputs.workspace == 'default' && inputs.environment || 'workspace' }} - path: infrastructure/toggles/feature-flags.json - plan-application-infrastructure: name: "Plan application infrastructure deployment to ${{ inputs.environment }} " concurrency: group: "${{ inputs.environment }}-${{ inputs.tag || inputs.workspace}}" cancel-in-progress: false - needs: - - prepare-appconfig uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ inputs.environment }} diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 81857246..8940ce01 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -49,34 +49,34 @@ jobs: build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} secrets: inherit - build-services: - name: "Build ${{ matrix.name }}" - needs: - - metadata - - quality-checks - strategy: - matrix: - include: - - name: "python" - build_type: "package" - uses: ./.github/workflows/build-project.yaml - with: - name: ${{ matrix.name }} - build_type: ${{ matrix.build_type }} - python_version: ${{ needs.metadata.outputs.python_version }} - commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.environment }} - repo_name: ${{ needs.metadata.outputs.reponame }} - workspace: ${{ needs.metadata.outputs.workspace }} - application_tag: ${{ inputs.application_tag || 'latest' }} - type: app - secrets: inherit + # build-services: + # name: "Build ${{ matrix.name }}" + # needs: + # - metadata + # - quality-checks + # strategy: + # matrix: + # include: + # - name: "python" + # build_type: "package" + # uses: ./.github/workflows/build-project.yaml + # with: + # name: ${{ matrix.name }} + # build_type: ${{ matrix.build_type }} + # python_version: ${{ needs.metadata.outputs.python_version }} + # commit_hash: ${{ needs.metadata.outputs.commit_hash }} + # environment: ${{ needs.metadata.outputs.environment }} + # repo_name: ${{ needs.metadata.outputs.reponame }} + # workspace: ${{ needs.metadata.outputs.workspace }} + # application_tag: ${{ inputs.application_tag || 'latest' }} + # type: app + # secrets: inherit perform-code-analysis: name: "Perform static code analysis" needs: - metadata - - build-services + # - build-services uses: ./.github/workflows/static-code-analysis.yaml with: environment: ${{ needs.metadata.outputs.environment }} @@ -86,7 +86,7 @@ jobs: name: "Deploy application infrastructure to the ${{ needs.metadata.outputs.environment }} environment" needs: - metadata - - build-services + # - build-services - perform-code-analysis uses: ./.github/workflows/deploy-application-infrastructure.yaml with: @@ -98,31 +98,9 @@ jobs: workflow_timeout: 30 secrets: inherit - deploy-data-migration-service: - name: "Deploy data migration service to ${{ needs.metadata.outputs.environment }}" - if: github.ref == 'refs/heads/main' && needs.metadata.outputs.environment != 'prod' - concurrency: - group: "${{ needs.metadata.outputs.environment }}-data-migration-${{ needs.metadata.outputs.workspace }}" - cancel-in-progress: false - needs: - - metadata - - deploy-application-infrastructure - uses: ./.github/workflows/deploy-data-migration-project.yaml - with: - name: "data-migration" - build_type: "service" - python_version: ${{ needs.metadata.outputs.python_version }} - commit_hash: ${{ needs.metadata.outputs.commit_hash }} - environment: ${{ needs.metadata.outputs.environment }} - repo_name: ${{ needs.metadata.outputs.reponame }} - workspace: ${{ needs.metadata.outputs.workspace }} - application_tag: ${{ inputs.application_tag || 'latest' }} - type: "app" - secrets: inherit - check-pipeline-status: name: "Check Pipeline Status" needs: - - deploy-data-migration-service + - deploy-application-infrastructure if: always() uses: ./.github/workflows/pipeline-status-check.yaml diff --git a/scripts/githooks/check-english-usage.sh b/scripts/githooks/check-english-usage.sh index 5a5a416f..10b9d659 100755 --- a/scripts/githooks/check-english-usage.sh +++ b/scripts/githooks/check-english-usage.sh @@ -62,7 +62,10 @@ function main() { function run-vale-natively() { # Read files into an array to handle spaces in filenames - mapfile -t files < <($filter) + files=() + while IFS= read -r file; do + files+=("$file") + done < <($filter) if [ ${#files[@]} -eq 0 ]; then echo "No files to check" return 0 @@ -89,7 +92,10 @@ function run-vale-in-docker() { local image=$(name=jdkato/vale docker-get-image-version-and-pull) # Read files into an array to handle spaces in filenames - mapfile -t files < <($filter) + files=() + while IFS= read -r file; do + files+=("$file") + done < <($filter) # We use /dev/null here to stop `vale` from complaining that it's # not been called correctly if the $filter happens to return an From 50cdd8474d873acd1c4909c0b663052e4308760a Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:15:46 +0000 Subject: [PATCH 079/181] fix: SAET-0000 Add default workspace for now --- .github/workflows/pipeline-deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 8940ce01..728c69da 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -91,7 +91,7 @@ jobs: uses: ./.github/workflows/deploy-application-infrastructure.yaml with: environment: ${{ needs.metadata.outputs.environment }} - workspace: ${{ needs.metadata.outputs.workspace }} + workspace: "default" application_tag: ${{ inputs.application_tag || 'latest' }} tag: ${{ inputs.tag }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} From c96a9e2e697168ae2296f4e55a3486e17b33bf98 Mon Sep 17 00:00:00 2001 From: ri-nhs <198660903+ri-nhs@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:35:01 +0000 Subject: [PATCH 080/181] fix: SAET-0000 fix pipeline issue --- infrastructure/stacks/triage/variables.tf | 291 ++++++++++------------ infrastructure/triage.tfvars | 4 - 2 files changed, 126 insertions(+), 169 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 0368621d..099c407e 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -1,175 +1,136 @@ -variable "project_name" { - description = "Project name prefix" - type = string -} +# variable "project_name" { +# description = "Project name prefix" +# type = string +# } # DynamoDB vars -variable "triage_table_name" { - type = string -} - -variable "billing" { - type = string - default = "PAY_PER_REQUEST" -} - -variable "hash_key" { - description = "primary partition key" - type = string -} - -variable "hash_key_type" { - description = "partition key type" - type = string - default = "S" -} - -variable "range_key" { - description = "sort key" - type = string -} +# variable "triage_table_name" { +# type = string +# } + +# variable "billing" { +# type = string +# default = "PAY_PER_REQUEST" +# } # IAM vars -variable "name" { - type = string -} - -variable "assume_services" { - description = "AWS services allowed to assume this role" - type = list(string) -} - -variable "policy_arns" { - description = "List of policy ARNS to attach" - type = list(string) -} - -variable "bucket_name" { - description = "S3 bucket name" - type = string -} - -variable "table_arns" { - description = "DynamoDB table ARNs" - type = list(string) -} +# variable "name" { +# type = string +# } + +# variable "policy_arns" { +# description = "List of policy ARNS to attach" +# type = list(string) +# } + +# variable "table_arns" { +# description = "DynamoDB table ARNs" +# type = list(string) +# } # Lambda vars -variable "function_name" { - type = string -} - -variable "handler" { - type = string -} - -variable "runtime" { - type = string -} - -variable "s3_key" { - type = string -} - -variable "lambda_role" { - type = string -} - -variable "mem_size" { - type = number -} - -variable "body_map_table" { - type = string - default = null -} - -variable "starting_node_table" { - type = string - default = null -} - -variable "triage_node_table" { - type = string - default = null -} - -variable "policy_jsons" { - description = "List of JSON policies for Lambda" - type = list(string) - default = [] -} +# variable "function_name" { +# type = string +# } + +# variable "handler" { +# type = string +# } + +# variable "runtime" { +# type = string +# } + +# variable "s3_key" { +# type = string +# } + +# variable "lambda_role" { +# type = string +# } + +# variable "mem_size" { +# type = number +# } + +# variable "body_map_table" { +# type = string +# default = null +# } + +# variable "starting_node_table" { +# type = string +# default = null +# } + +# variable "triage_node_table" { +# type = string +# default = null +# } + +# variable "policy_jsons" { +# description = "List of JSON policies for Lambda" +# type = list(string) +# default = [] +# } # REST API vars -variable "resources" { - type = list(object({ - path_part = string - method = string - lambda_invoke_arn = string - })) -} - -variable "authorization" { - type = string -} - -variable "type" { - type = string - default = "AWS_PROXY" -} - -variable "int_http_method" { - type = string - default = "POST" -} - -variable "stage_name" { - type = string -} - -# S3 vars -variable "region" { - description = "AWS region for S3 bucket" - type = string -} +# variable "resources" { +# type = list(object({ +# path_part = string +# method = string +# lambda_invoke_arn = string +# })) +# } + +# variable "type" { +# type = string +# default = "AWS_PROXY" +# } + +# variable "int_http_method" { +# type = string +# default = "POST" +# } + +# variable "stage_name" { +# type = string +# } + # Trigger vars -variable "statement_id" { - type = string -} - -variable "action" { - type = string -} - -variable "lambda_name" { - type = string -} - -variable "principal" { - type = string -} - -variable "bucket_arn" { - type = string - default = null -} - -variable "lambda_arn" { - type = string -} - -variable "events" { - type = list(string) - default = [] -} - -variable "filter_prefix" { - type = string - default = null -} - -variable "api_gateway_source_arn" { - type = string - default = null -} +# variable "statement_id" { +# type = string +# } + +# variable "lambda_name" { +# type = string +# } + +# variable "principal" { +# type = string +# } + +# variable "bucket_arn" { +# type = string +# default = null +# } + +# variable "lambda_arn" { +# type = string +# } + +# variable "events" { +# type = list(string) +# default = [] +# } + +# variable "filter_prefix" { +# type = string +# default = null +# } + +# variable "api_gateway_source_arn" { +# type = string +# default = null +# } diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index 28c6f84d..97700499 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -1,7 +1,3 @@ -aws_region = "eu-west-2" -environment = "test" -project_name = "saet" - #Lambda mem_size = 512 runtime = "python3.13" From 161a1b8239bcfdd0fe5fe997c3439778e018871b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 6 Feb 2026 12:56:35 +0000 Subject: [PATCH 081/181] Adding lambda --- infrastructure/stacks/triage/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 1391e26f..b292a6fd 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -22,7 +22,7 @@ module "s3lambda" { var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} ) -# } + } # module "apiglambda" { # source = "../../modules/lambda" From 46586c3b038427b390954cca40f0f70182f7d6de Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 6 Feb 2026 14:16:09 +0000 Subject: [PATCH 082/181] Adding lambda --- infrastructure/stacks/triage/lambda.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index b292a6fd..a4b14814 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -1,6 +1,7 @@ module "s3lambda" { source = "../../modules/lambda" + aws_region = var.aws_region function_name = "${var.project_name}-${var.environment}s3-Lambda" policy_jsons = var.policy_jsons handler = "s3_configurator.handler" @@ -22,7 +23,7 @@ module "s3lambda" { var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} ) - } +} # module "apiglambda" { # source = "../../modules/lambda" From 5d889c3fe959a351600b76d227da4b183c1bb8a2 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 14:10:46 +0000 Subject: [PATCH 083/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/iam.tf | 155 ++++++++++++++----------- infrastructure/stacks/triage/lambda.tf | 26 ++--- 2 files changed, 100 insertions(+), 81 deletions(-) diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index 36810ed7..912572e6 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -1,75 +1,96 @@ -# resource "aws_iam_role" "lambda_role" { -# assume_role_policy = jsonencode({ -# Version = "2012-10-17", -# Statement = [{ -# Effect = "Allow", -# Principal = { -# Service = var.assume_services -# }, -# Action = "sts:AssumeRole" -# }] -# }) -# name = var.name -# } +# locals { +# s3_lambda_policy = jsondecode(templatefile("${path.module}/s3_lambda_policy.json.tpl", { +# project = var.project +# repo_name = var.repo_name +# })) -# resource "aws_iam_policy" "s3_access" { -# name = "${var.name}-s3-access" -# policy = jsonencode({ -# Version = "2012-10-17", -# Statement = [ -# { -# Effect = "Allow", -# Action = [ -# "s3:GetObject", -# "s3:PutObject", -# "s3:ListBucket" -# ], -# Resource = [ -# # bucket itself -# "arn:aws:s3:::${var.bucket_name}", -# # all objects inside -# "arn:aws:s3:::${var.bucket_name}/*" -# ] -# }] -# }) +# dynamodb_policy = jsondecode(templatefile("${path.module}/apig_lambda_policy.json.tpl", { +# project = var.project +# repo_name = var.repo_name +# })) # } -# resource "aws_iam_policy" "ddb_access" { -# name = "${var.name}-dynamo-db-access" -# policy = jsonencode({ -# Version = "2012-10-17", -# Statement = [ -# { -# Effect = "Allow", -# Action = [ -# "dynamodb:GetItem", -# "dynamodb:BatchGetItem", -# "dynamodb:BatchWriteItem", -# "dynamodb:Query", -# "dynamodb:Scan" -# ], -# Resource = concat( -# var.table_arns, -# [for q in var.table_arns : "${q}/index/*"] -# ) -# }] -# }) -# } -# resource "aws_iam_role_policy_attachment" "s3_attach" { -# role = aws_iam_role.lambda_role.name -# policy_arn = aws_iam_policy.s3_access.arn -# } +resource "aws_iam_role" "lambda_role" { + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Effect = "Allow", + Principal = { + Service = "lambda.amazonaws.com" + }, + Action = "sts:AssumeRole" + }] + }) + name = "${local.resource_prefix}-lambda-role" +} -# resource "aws_iam_role_policy_attachment" "ddb_aatach" { -# role = aws_iam_role.lambda_role.name -# policy_arn = aws_iam_policy.ddb_access.arn -# } +resource "aws_iam_policy" "s3_access" { + name = "${local.resource_prefix}-s3-access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "s3:GetObject", + "s3:PutObject", + "s3:ListBucket" + ], + Resource = [ + # bucket itself + "arn:aws:s3:::${module.pathway_artifact_bucket.bucket_name}", + # all objects inside + "arn:aws:s3:::${module.pathway_artifact_bucket.bucket_name}/*" + ] + }] + }) +} -# resource "aws_iam_role_policy_attachment" "attachments" { -# for_each = toset(var.policy_arns) +resource "aws_iam_policy" "ddb_access" { + name = "${local.resource_prefix}-dynamo-db-access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:Query", + "dynamodb:Scan" + ], + Resource = concat( + [ + module.starting_coords.table_arn, + module.triage_nodes.table_arn, + module.bodymaps.table_arn + ], + [ + "${module.starting_coords.table_arn}/index/*", + "${module.triage_nodes.table_arn}/index/*", + "${module.bodymaps.table_arn}/index/*" + ] + ) + }] + }) +} -# role = aws_iam_role.lambda_role.name -# policy_arn = each.value -# } +resource "aws_iam_role_policy_attachment" "s3_attach" { + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.s3_access.arn +} + +resource "aws_iam_role_policy_attachment" "ddb_aatach" { + role = aws_iam_role.lambda_role.name + policy_arn = aws_iam_policy.ddb_access.arn +} + +resource "aws_iam_role_policy_attachment" "attachments" { + for_each = toset(var.policy_arns) + + role = aws_iam_role.lambda_role.name + policy_arn = each.value +} diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a4b14814..0bf22baf 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -2,26 +2,24 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${var.project_name}-${var.environment}s3-Lambda" - policy_jsons = var.policy_jsons + function_name = "${local.resource_prefix}-s3-Lambda" + policy_jsons = [aws_iam_policy.s3_access.policy] handler = "s3_configurator.handler" - s3_bucket_name = module.SAETBucket.bucket_name + s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size - environment_variables = merge( - { - "ENVIRONMENT" = var.environment - "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace - "PROJECT_NAME" = var.project_name - }, - var.bucket_name != null ? { "DATA_RELEASE_BUCKET" = var.bucket_name } : {}, - var.body_map_table != null ? { "BODY_MAP_NODE_TABLE" = var.body_map_table } : {}, - var.starting_node_table != null ? { "STARTING_NODE_TABLE" = var.starting_node_table } : {}, - var.triage_node_table != null ? { "TRIAGE_NODE_TABLE" = var.triage_node_table } : {} - ) + environment_variables = { + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "PROJECT_NAME" = var.project_name + "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.bucket_name + "BODY_MAP_NODE_TABLE" = module.bodymaps.table_name + "STARTING_NODE_TABLE" = module.starting_coords.table_name + "TRIAGE_NODE_TABLE" = module.triage_nodes.table_name + } } From a52d2360f7825abe78456535c337b64b6f8035db Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 14:18:39 +0000 Subject: [PATCH 084/181] adding Iam and lambda and fixing these --- infrastructure/stacks/triage/iam.tf | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index 912572e6..a4efb48d 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -39,9 +39,9 @@ resource "aws_iam_policy" "s3_access" { ], Resource = [ # bucket itself - "arn:aws:s3:::${module.pathway_artifact_bucket.bucket_name}", + "arn:aws:s3:::${module.pathway_artifact_bucket.s3_bucket_id}", # all objects inside - "arn:aws:s3:::${module.pathway_artifact_bucket.bucket_name}/*" + "arn:aws:s3:::${module.pathway_artifact_bucket.s3_bucket_id}/*" ] }] }) @@ -63,14 +63,14 @@ resource "aws_iam_policy" "ddb_access" { ], Resource = concat( [ - module.starting_coords.table_arn, - module.triage_nodes.table_arn, - module.bodymaps.table_arn + module.starting_coords.dynamodb_table_arn, + module.triage_nodes.dynamodb_table_arn, + module.bodymaps.dynamodb_table_arn ], [ - "${module.starting_coords.table_arn}/index/*", - "${module.triage_nodes.table_arn}/index/*", - "${module.bodymaps.table_arn}/index/*" + "${module.starting_coords.dynamodb_table_arn}/index/*", + "${module.triage_nodes.dynamodb_table_arn}/index/*", + "${module.bodymaps.dynamodb_table_arn}/index/*" ] ) }] From af60a7bba93e0aadd6f946e5f340058b365ca5c0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 14:23:01 +0000 Subject: [PATCH 085/181] adding Iam and lambda and fixing these --- infrastructure/stacks/triage/variables.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 099c407e..4108b10a 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -18,10 +18,10 @@ # type = string # } -# variable "policy_arns" { -# description = "List of policy ARNS to attach" -# type = list(string) -# } +variable "policy_arns" { + description = "List of policy ARNS to attach" + type = list(string) +} # variable "table_arns" { # description = "DynamoDB table ARNs" From 7b7478a0b860230388072f959caf172e47bca757 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 14:41:16 +0000 Subject: [PATCH 086/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/variables.tf | 8 ++++---- infrastructure/stacks/triage/lambda.tf | 1 - infrastructure/stacks/triage/variables.tf | 18 +++++++++--------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 9f764b26..87ba8e5b 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -115,10 +115,10 @@ variable "allowed_triggers" { # type = string # } -# variable "account_id" { -# description = "AWS account ID" -# type = string -# } +variable "account_id" { + description = "AWS account ID" + type = string +} variable "aws_region" { description = "AWS region where the Lambda function will be deployed" diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 0bf22baf..68fa6c90 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -14,7 +14,6 @@ module "s3lambda" { environment_variables = { "ENVIRONMENT" = var.environment "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace - "PROJECT_NAME" = var.project_name "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.bucket_name "BODY_MAP_NODE_TABLE" = module.bodymaps.table_name "STARTING_NODE_TABLE" = module.starting_coords.table_name diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 4108b10a..d5ea712f 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -37,21 +37,21 @@ variable "policy_arns" { # type = string # } -# variable "runtime" { -# type = string -# } +variable "runtime" { + type = string +} -# variable "s3_key" { -# type = string -# } +variable "s3_key" { + type = string +} # variable "lambda_role" { # type = string # } -# variable "mem_size" { -# type = number -# } +variable "mem_size" { + type = number +} # variable "body_map_table" { # type = string From b1c158c9a5d401232eae32bd0164fd1a8ca1baaa Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 15:10:28 +0000 Subject: [PATCH 087/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/variables.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 87ba8e5b..9f764b26 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -115,10 +115,10 @@ variable "allowed_triggers" { # type = string # } -variable "account_id" { - description = "AWS account ID" - type = string -} +# variable "account_id" { +# description = "AWS account ID" +# type = string +# } variable "aws_region" { description = "AWS region where the Lambda function will be deployed" From e6b733a92a5528c554a5d25fe85e033aca97f342 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 15:18:28 +0000 Subject: [PATCH 088/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/variables.tf | 8 ++++---- infrastructure/stacks/triage/lambda.tf | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 9f764b26..87ba8e5b 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -115,10 +115,10 @@ variable "allowed_triggers" { # type = string # } -# variable "account_id" { -# description = "AWS account ID" -# type = string -# } +variable "account_id" { + description = "AWS account ID" + type = string +} variable "aws_region" { description = "AWS region where the Lambda function will be deployed" diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 68fa6c90..2a7fc8c3 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -12,14 +12,14 @@ module "s3lambda" { environment_variables = { - "ENVIRONMENT" = var.environment - "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace - "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.bucket_name - "BODY_MAP_NODE_TABLE" = module.bodymaps.table_name - "STARTING_NODE_TABLE" = module.starting_coords.table_name - "TRIAGE_NODE_TABLE" = module.triage_nodes.table_name + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.s3_bucket_id + "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_arn + "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_arn + "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_arn } - + account_id = data.aws_caller_identity.current.account_id } # module "apiglambda" { From 5db22feefdad54e06a14a5c89db5d36399eb7650 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 9 Feb 2026 15:26:32 +0000 Subject: [PATCH 089/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/data.tf | 188 +++++++++++++------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/infrastructure/modules/lambda/data.tf b/infrastructure/modules/lambda/data.tf index 4a8d12bc..49e404f4 100644 --- a/infrastructure/modules/lambda/data.tf +++ b/infrastructure/modules/lambda/data.tf @@ -1,99 +1,99 @@ -data "aws_iam_policy_document" "vpc_access_policy" { - # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - statement { - sid = "AllowVpcAccess" - effect = "Allow" - actions = [ - "ec2:CreateNetworkInterface", - "ec2:DescribeNetworkInterfaces", - "ec2:DeleteNetworkInterface", - "ec2:DescribeSubnets", - "ec2:AssignPrivateIpAddresses", - "ec2:UnassignPrivateIpAddresses" - ] - resources = [ - "*" - ] - } -} +# data "aws_iam_policy_document" "vpc_access_policy" { +# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# statement { +# sid = "AllowVpcAccess" +# effect = "Allow" +# actions = [ +# "ec2:CreateNetworkInterface", +# "ec2:DescribeNetworkInterfaces", +# "ec2:DeleteNetworkInterface", +# "ec2:DescribeSubnets", +# "ec2:AssignPrivateIpAddresses", +# "ec2:UnassignPrivateIpAddresses" +# ] +# resources = [ +# "*" +# ] +# } +# } -data "aws_iam_policy_document" "deny_lambda_function_access_policy" { - # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - statement { - sid = "DenyLambdaFunctionAccess" - effect = "Deny" - actions = [ - "ec2:CreateNetworkInterface", - "ec2:DeleteNetworkInterface", - "ec2:DescribeNetworkInterfaces", - "ec2:DescribeSubnets", - "ec2:DetachNetworkInterface", - "ec2:AssignPrivateIpAddresses", - "ec2:UnassignPrivateIpAddresses" - ] - resources = ["*"] - condition { - test = "ArnEquals" - variable = "lambda:SourceFunctionArn" - values = [ - "arn:aws:lambda:${var.aws_region}:${var.account_id}:function:${var.function_name}${local.workspace_suffix}" - ] - } - } -} +# data "aws_iam_policy_document" "deny_lambda_function_access_policy" { +# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# statement { +# sid = "DenyLambdaFunctionAccess" +# effect = "Deny" +# actions = [ +# "ec2:CreateNetworkInterface", +# "ec2:DeleteNetworkInterface", +# "ec2:DescribeNetworkInterfaces", +# "ec2:DescribeSubnets", +# "ec2:DetachNetworkInterface", +# "ec2:AssignPrivateIpAddresses", +# "ec2:UnassignPrivateIpAddresses" +# ] +# resources = ["*"] +# condition { +# test = "ArnEquals" +# variable = "lambda:SourceFunctionArn" +# values = [ +# "arn:aws:lambda:${var.aws_region}:${var.account_id}:function:${var.function_name}${local.workspace_suffix}" +# ] +# } +# } +# } -data "aws_iam_policy_document" "allow_private_subnet_policy" { - # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - statement { - sid = "AllowPrivateSubnetAccess" - effect = "Allow" - actions = [ - "lambda:CreateFunction", - "lambda:UpdateFunctionConfiguration" - ] - resources = ["*"] - condition { - test = "ForAllValues:StringEquals" - variable = "lambda:SubnetIds" - values = var.subnet_ids - } - } -} +# data "aws_iam_policy_document" "allow_private_subnet_policy" { +# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# statement { +# sid = "AllowPrivateSubnetAccess" +# effect = "Allow" +# actions = [ +# "lambda:CreateFunction", +# "lambda:UpdateFunctionConfiguration" +# ] +# resources = ["*"] +# condition { +# test = "ForAllValues:StringEquals" +# variable = "lambda:SubnetIds" +# values = var.subnet_ids +# } +# } +# } -data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { - # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { +# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - statement { - sid = "EnforceStayInSpecificVpc" - effect = "Allow" - actions = [ - "lambda:CreateFunction", - "lambda:UpdateFunctionConfiguration" - ] - resources = ["*"] - condition { - test = "StringEquals" - variable = "lambda:VpcIds" - values = [var.vpc_id] - } - } -} +# statement { +# sid = "EnforceStayInSpecificVpc" +# effect = "Allow" +# actions = [ +# "lambda:CreateFunction", +# "lambda:UpdateFunctionConfiguration" +# ] +# resources = ["*"] +# condition { +# test = "StringEquals" +# variable = "lambda:VpcIds" +# values = [var.vpc_id] +# } +# } +# } -data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { - # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 - statement { - sid = "EnforceVpcFunction" - effect = "Deny" - actions = [ - "lambda:CreateFunction", - "lambda:UpdateFunctionConfiguration" - ] - resources = ["*"] - condition { - test = "Null" - variable = "lambda:VpcIds" - values = ["true"] - } - } -} +# data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { +# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +# statement { +# sid = "EnforceVpcFunction" +# effect = "Deny" +# actions = [ +# "lambda:CreateFunction", +# "lambda:UpdateFunctionConfiguration" +# ] +# resources = ["*"] +# condition { +# test = "Null" +# variable = "lambda:VpcIds" +# values = ["true"] +# } +# } +# } From c0479c5fd844a45d59a1be96446b529f6f76a3b9 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 10:08:48 +0000 Subject: [PATCH 090/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/data.tf | 188 ++++++++++----------- infrastructure/modules/lambda/variables.tf | 88 +++++----- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/infrastructure/modules/lambda/data.tf b/infrastructure/modules/lambda/data.tf index 49e404f4..4a8d12bc 100644 --- a/infrastructure/modules/lambda/data.tf +++ b/infrastructure/modules/lambda/data.tf @@ -1,99 +1,99 @@ -# data "aws_iam_policy_document" "vpc_access_policy" { -# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# statement { -# sid = "AllowVpcAccess" -# effect = "Allow" -# actions = [ -# "ec2:CreateNetworkInterface", -# "ec2:DescribeNetworkInterfaces", -# "ec2:DeleteNetworkInterface", -# "ec2:DescribeSubnets", -# "ec2:AssignPrivateIpAddresses", -# "ec2:UnassignPrivateIpAddresses" -# ] -# resources = [ -# "*" -# ] -# } -# } +data "aws_iam_policy_document" "vpc_access_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "AllowVpcAccess" + effect = "Allow" + actions = [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:DescribeSubnets", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ] + resources = [ + "*" + ] + } +} -# data "aws_iam_policy_document" "deny_lambda_function_access_policy" { -# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# statement { -# sid = "DenyLambdaFunctionAccess" -# effect = "Deny" -# actions = [ -# "ec2:CreateNetworkInterface", -# "ec2:DeleteNetworkInterface", -# "ec2:DescribeNetworkInterfaces", -# "ec2:DescribeSubnets", -# "ec2:DetachNetworkInterface", -# "ec2:AssignPrivateIpAddresses", -# "ec2:UnassignPrivateIpAddresses" -# ] -# resources = ["*"] -# condition { -# test = "ArnEquals" -# variable = "lambda:SourceFunctionArn" -# values = [ -# "arn:aws:lambda:${var.aws_region}:${var.account_id}:function:${var.function_name}${local.workspace_suffix}" -# ] -# } -# } -# } +data "aws_iam_policy_document" "deny_lambda_function_access_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "DenyLambdaFunctionAccess" + effect = "Deny" + actions = [ + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DetachNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ] + resources = ["*"] + condition { + test = "ArnEquals" + variable = "lambda:SourceFunctionArn" + values = [ + "arn:aws:lambda:${var.aws_region}:${var.account_id}:function:${var.function_name}${local.workspace_suffix}" + ] + } + } +} -# data "aws_iam_policy_document" "allow_private_subnet_policy" { -# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# statement { -# sid = "AllowPrivateSubnetAccess" -# effect = "Allow" -# actions = [ -# "lambda:CreateFunction", -# "lambda:UpdateFunctionConfiguration" -# ] -# resources = ["*"] -# condition { -# test = "ForAllValues:StringEquals" -# variable = "lambda:SubnetIds" -# values = var.subnet_ids -# } -# } -# } +data "aws_iam_policy_document" "allow_private_subnet_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "AllowPrivateSubnetAccess" + effect = "Allow" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "ForAllValues:StringEquals" + variable = "lambda:SubnetIds" + values = var.subnet_ids + } + } +} -# data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { -# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 +data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# statement { -# sid = "EnforceStayInSpecificVpc" -# effect = "Allow" -# actions = [ -# "lambda:CreateFunction", -# "lambda:UpdateFunctionConfiguration" -# ] -# resources = ["*"] -# condition { -# test = "StringEquals" -# variable = "lambda:VpcIds" -# values = [var.vpc_id] -# } -# } -# } + statement { + sid = "EnforceStayInSpecificVpc" + effect = "Allow" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "StringEquals" + variable = "lambda:VpcIds" + values = [var.vpc_id] + } + } +} -# data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { -# # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 -# statement { -# sid = "EnforceVpcFunction" -# effect = "Deny" -# actions = [ -# "lambda:CreateFunction", -# "lambda:UpdateFunctionConfiguration" -# ] -# resources = ["*"] -# condition { -# test = "Null" -# variable = "lambda:VpcIds" -# values = ["true"] -# } -# } -# } +data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { + # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 + statement { + sid = "EnforceVpcFunction" + effect = "Deny" + actions = [ + "lambda:CreateFunction", + "lambda:UpdateFunctionConfiguration" + ] + resources = ["*"] + condition { + test = "Null" + variable = "lambda:VpcIds" + values = ["true"] + } + } +} diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 87ba8e5b..8eac99e3 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -28,35 +28,35 @@ variable "runtime" { default = "python3.12" } -# variable "publish" { -# description = "Whether to publish a new Lambda version on update" -# default = true -# } +variable "publish" { + description = "Whether to publish a new Lambda version on update" + default = true +} -# variable "create_package" { -# description = "Whether to create a new ZIP package or use an existing one" -# default = false -# } +variable "create_package" { + description = "Whether to create a new ZIP package or use an existing one" + default = false +} -# variable "local_existing_package" { -# description = "Path to the local ZIP file if using a pre-existing package" -# default = "./misc/init.zip" -# } +variable "local_existing_package" { + description = "Path to the local ZIP file if using a pre-existing package" + default = "./misc/init.zip" +} -# variable "ignore_source_code_hash" { -# description = "Whether to ignore changes to the source code hash" -# default = true -# } +variable "ignore_source_code_hash" { + description = "Whether to ignore changes to the source code hash" + default = true +} -# variable "attach_tracing_policy" { -# default = false -# } +variable "attach_tracing_policy" { + default = false +} -# variable "tracing_mode" { -# description = "Tracing configuration for the Lambda function" -# type = string -# default = "PassThrough" -# } +variable "tracing_mode" { + description = "Tracing configuration for the Lambda function" + type = string + default = "PassThrough" +} variable "attach_policy_jsons" { description = "Whether to attach the provided JSON policies to the Lambda role" @@ -88,13 +88,13 @@ variable "timeout" { default = "3" } -# variable "subnet_ids" { -# description = "List of subnet IDs for the Lambda function VPC configuration" -# } +variable "subnet_ids" { + description = "List of subnet IDs for the Lambda function VPC configuration" +} -# variable "security_group_ids" { -# description = "List of security group IDs for the Lambda function VPC configuration" -# } +variable "security_group_ids" { + description = "List of security group IDs for the Lambda function VPC configuration" +} variable "s3_bucket_name" { description = "Name of the S3 bucket where the Lambda package is stored" @@ -125,19 +125,19 @@ variable "aws_region" { type = string } -# variable "vpc_id" { -# description = "Id of the VPC into which the Lambda function will be deployed" -# type = string -# } +variable "vpc_id" { + description = "Id of the VPC into which the Lambda function will be deployed" + type = string +} -# variable "cloudwatch_logs_retention" { -# description = "Number of days to retain CloudWatch logs" -# type = number -# default = 30 -# } +variable "cloudwatch_logs_retention" { + description = "Number of days to retain CloudWatch logs" + type = number + default = 30 +} -# variable "cloudwatch_log_level" { -# description = "Logging level for CloudWatch logs" -# type = string -# default = "INFO" -# } +variable "cloudwatch_log_level" { + description = "Logging level for CloudWatch logs" + type = string + default = "INFO" +} From d7d4d07692fad97db43119a1b1ae872163de8649 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 11:22:52 +0000 Subject: [PATCH 091/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/data.tf | 5 +++++ infrastructure/modules/lambda/locals.tf | 18 +++++++++++------- infrastructure/modules/lambda/variables.tf | 5 +++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/infrastructure/modules/lambda/data.tf b/infrastructure/modules/lambda/data.tf index 4a8d12bc..6f18d5f1 100644 --- a/infrastructure/modules/lambda/data.tf +++ b/infrastructure/modules/lambda/data.tf @@ -1,4 +1,5 @@ data "aws_iam_policy_document" "vpc_access_policy" { + count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { @@ -19,6 +20,7 @@ data "aws_iam_policy_document" "vpc_access_policy" { } data "aws_iam_policy_document" "deny_lambda_function_access_policy" { + count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "DenyLambdaFunctionAccess" @@ -44,6 +46,7 @@ data "aws_iam_policy_document" "deny_lambda_function_access_policy" { } data "aws_iam_policy_document" "allow_private_subnet_policy" { + count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "AllowPrivateSubnetAccess" @@ -62,6 +65,7 @@ data "aws_iam_policy_document" "allow_private_subnet_policy" { } data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { + count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { @@ -81,6 +85,7 @@ data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { } data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { + count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "EnforceVpcFunction" diff --git a/infrastructure/modules/lambda/locals.tf b/infrastructure/modules/lambda/locals.tf index 93069cfc..a372c93e 100644 --- a/infrastructure/modules/lambda/locals.tf +++ b/infrastructure/modules/lambda/locals.tf @@ -5,11 +5,15 @@ locals { workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" environment_workspace = "${terraform.workspace}" == "default" ? "" : "${terraform.workspace}" - additional_json_policies = (concat(var.policy_jsons, [ - data.aws_iam_policy_document.allow_private_subnet_policy.json, - data.aws_iam_policy_document.limit_to_environment_vpc_policy.json, - data.aws_iam_policy_document.enforce_vpc_lambda_policy.json, - data.aws_iam_policy_document.deny_lambda_function_access_policy.json, - data.aws_iam_policy_document.vpc_access_policy.json - ])) + enable_vpc_policies = var.subnet_ids != null && length(var.subnet_ids) > 0 && var.vpc_id != null && var.vpc_id != "" + additional_json_policies = concat( + var.policy_jsons, + local.enable_vpc_policies ? [ + data.aws_iam_policy_document.allow_private_subnet_policy[0].json, + data.aws_iam_policy_document.limit_to_environment_vpc_policy[0].json, + data.aws_iam_policy_document.enforce_vpc_lambda_policy[0].json, + data.aws_iam_policy_document.deny_lambda_function_access_policy[0].json, + data.aws_iam_policy_document.vpc_access_policy[0].json + ] : [] + ) } diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 8eac99e3..3c245f6f 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -90,10 +90,14 @@ variable "timeout" { variable "subnet_ids" { description = "List of subnet IDs for the Lambda function VPC configuration" + type = list(string) + default = null } variable "security_group_ids" { description = "List of security group IDs for the Lambda function VPC configuration" + type = list(string) + default = null } variable "s3_bucket_name" { @@ -128,6 +132,7 @@ variable "aws_region" { variable "vpc_id" { description = "Id of the VPC into which the Lambda function will be deployed" type = string + default = null } variable "cloudwatch_logs_retention" { From 46dc4b83c4147727a2d97c5cecfd454aef01769a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 11:39:19 +0000 Subject: [PATCH 092/181] adding Iam and lambda and fixing these --- infrastructure/stacks/triage/lambda.tf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 2a7fc8c3..4a478c61 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -10,6 +10,9 @@ module "s3lambda" { s3_key = var.s3_key memory_size = var.mem_size + subnet_ids = null + security_group_ids = null + vpc_id = null environment_variables = { "ENVIRONMENT" = var.environment From aea674247206d889db566375c3ab02bbfa582c60 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 11:48:05 +0000 Subject: [PATCH 093/181] adding Iam and lambda and fixing these --- infrastructure/stacks/triage/lambda.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 4a478c61..a2f7962d 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -9,6 +9,7 @@ module "s3lambda" { runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size + description = "Lambda function for S3 event processing in SAET Triage API" subnet_ids = null security_group_ids = null From b90050203ee416bb65b48a9ca97261e8bb4bb8c2 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 11:57:04 +0000 Subject: [PATCH 094/181] adding Iam and lambda and fixing these --- infrastructure/modules/lambda/variables.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/modules/lambda/variables.tf b/infrastructure/modules/lambda/variables.tf index 3c245f6f..a8f602b4 100644 --- a/infrastructure/modules/lambda/variables.tf +++ b/infrastructure/modules/lambda/variables.tf @@ -5,9 +5,9 @@ variable "function_name" { description = "The function name of the Lambda" } -# variable "description" { -# description = "The description of the Lambda" -# } +variable "description" { + description = "The description of the Lambda" +} variable "policy_jsons" { description = "List of JSON policies for Lambda" From 40b36924056d9dbf2f6df57f0c69c261456d0dbe Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 14:41:18 +0000 Subject: [PATCH 095/181] Added extra information into the pre commit file --- .tool-versions | 1 + infrastructure/stacks/triage/iam.tf | 10 +++++----- infrastructure/stacks/triage/lambda.tf | 2 +- infrastructure/stacks/triage/variables.tf | 5 ----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.tool-versions b/.tool-versions index b6d368b1..bf46170d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -3,6 +3,7 @@ terraform 1.7.0 pre-commit 3.6.0 gitleaks 8.18.4 +vale 3.6.0 # ============================================================================== # The section below is reserved for Docker image versions. diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index a4efb48d..c29320aa 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -87,10 +87,10 @@ resource "aws_iam_role_policy_attachment" "ddb_aatach" { policy_arn = aws_iam_policy.ddb_access.arn } -resource "aws_iam_role_policy_attachment" "attachments" { - for_each = toset(var.policy_arns) +# resource "aws_iam_role_policy_attachment" "attachments" { +# for_each = toset(var.policy_arns) - role = aws_iam_role.lambda_role.name - policy_arn = each.value -} +# role = aws_iam_role.lambda_role.name +# policy_arn = each.value +# } diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a2f7962d..8c37a2c7 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -9,7 +9,7 @@ module "s3lambda" { runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size - description = "Lambda function for S3 event processing in SAET Triage API" + description = "Lambda function for S3 event processing in SAET Triage API" subnet_ids = null security_group_ids = null diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index d5ea712f..fd55a0b4 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -18,11 +18,6 @@ # type = string # } -variable "policy_arns" { - description = "List of policy ARNS to attach" - type = list(string) -} - # variable "table_arns" { # description = "DynamoDB table ARNs" # type = list(string) From 5185aa9dbd5bba73f4422cd8bb543ab950c3d079 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 16:00:22 +0000 Subject: [PATCH 096/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/data.tf | 23 +++++++++++++++++++++++ infrastructure/stacks/triage/variables.tf | 6 ++++++ 2 files changed, 29 insertions(+) create mode 100644 infrastructure/stacks/triage/data.tf diff --git a/infrastructure/stacks/triage/data.tf b/infrastructure/stacks/triage/data.tf new file mode 100644 index 00000000..4ef360ac --- /dev/null +++ b/infrastructure/stacks/triage/data.tf @@ -0,0 +1,23 @@ +data "aws_vpc" "vpc" { + filter { + name = "tag:Name" + values = ["${local.account_prefix}-vpc"] + } +} + +data "aws_subnets" "private_subnets" { + filter { + name = "vpc-id" + values = [data.aws_vpc.vpc.id] + } + + filter { + name = "tag:Name" + values = ["${local.account_prefix}-vpc-private-*"] + } + + filter { + name = "tag:CidrRange" + values = [var.vpc_private_subnet_cidr_range] + } +} diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index fd55a0b4..78c949f2 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -69,6 +69,12 @@ variable "mem_size" { # default = [] # } +variable "vpc_private_subnet_cidr_range" { + description = "The CIDR range for the VPC private subnets" + type = string + default = "24" +} + # REST API vars # variable "resources" { # type = list(object({ From b1d5d3a7531d34f3f49b5177b36719947e960fc0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 16:03:17 +0000 Subject: [PATCH 097/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/lambda.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 8c37a2c7..a3fcf45c 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -11,9 +11,9 @@ module "s3lambda" { memory_size = var.mem_size description = "Lambda function for S3 event processing in SAET Triage API" - subnet_ids = null - security_group_ids = null - vpc_id = null + # subnet_ids = null + # security_group_ids = null + # vpc_id = null environment_variables = { "ENVIRONMENT" = var.environment From ec887cd1f7ae703afcb6648539c4154dd77fefb0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 10 Feb 2026 16:14:38 +0000 Subject: [PATCH 098/181] Added extra information into the pre commit file --- infrastructure/modules/lambda/locals.tf | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/infrastructure/modules/lambda/locals.tf b/infrastructure/modules/lambda/locals.tf index a372c93e..93069cfc 100644 --- a/infrastructure/modules/lambda/locals.tf +++ b/infrastructure/modules/lambda/locals.tf @@ -5,15 +5,11 @@ locals { workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" environment_workspace = "${terraform.workspace}" == "default" ? "" : "${terraform.workspace}" - enable_vpc_policies = var.subnet_ids != null && length(var.subnet_ids) > 0 && var.vpc_id != null && var.vpc_id != "" - additional_json_policies = concat( - var.policy_jsons, - local.enable_vpc_policies ? [ - data.aws_iam_policy_document.allow_private_subnet_policy[0].json, - data.aws_iam_policy_document.limit_to_environment_vpc_policy[0].json, - data.aws_iam_policy_document.enforce_vpc_lambda_policy[0].json, - data.aws_iam_policy_document.deny_lambda_function_access_policy[0].json, - data.aws_iam_policy_document.vpc_access_policy[0].json - ] : [] - ) + additional_json_policies = (concat(var.policy_jsons, [ + data.aws_iam_policy_document.allow_private_subnet_policy.json, + data.aws_iam_policy_document.limit_to_environment_vpc_policy.json, + data.aws_iam_policy_document.enforce_vpc_lambda_policy.json, + data.aws_iam_policy_document.deny_lambda_function_access_policy.json, + data.aws_iam_policy_document.vpc_access_policy.json + ])) } From e1f1818fc8a95e34ba3330191e553a4aadea44cd Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 09:30:23 +0000 Subject: [PATCH 099/181] Added extra information into the pre commit file --- infrastructure/modules/lambda/data.tf | 5 ----- 1 file changed, 5 deletions(-) diff --git a/infrastructure/modules/lambda/data.tf b/infrastructure/modules/lambda/data.tf index 6f18d5f1..4a8d12bc 100644 --- a/infrastructure/modules/lambda/data.tf +++ b/infrastructure/modules/lambda/data.tf @@ -1,5 +1,4 @@ data "aws_iam_policy_document" "vpc_access_policy" { - count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 # checkov:skip=CKV_AWS_356: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { @@ -20,7 +19,6 @@ data "aws_iam_policy_document" "vpc_access_policy" { } data "aws_iam_policy_document" "deny_lambda_function_access_policy" { - count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "DenyLambdaFunctionAccess" @@ -46,7 +44,6 @@ data "aws_iam_policy_document" "deny_lambda_function_access_policy" { } data "aws_iam_policy_document" "allow_private_subnet_policy" { - count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "AllowPrivateSubnetAccess" @@ -65,7 +62,6 @@ data "aws_iam_policy_document" "allow_private_subnet_policy" { } data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { - count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { @@ -85,7 +81,6 @@ data "aws_iam_policy_document" "limit_to_environment_vpc_policy" { } data "aws_iam_policy_document" "enforce_vpc_lambda_policy" { - count = local.enable_vpc_policies ? 1 : 0 # checkov:skip=CKV_AWS_111: TODO https://nhsd-jira.digital.nhs.uk/browse/FDOS-421 statement { sid = "EnforceVpcFunction" From 447c29e801e2825dda594092cd8ee7c36c0fc2a7 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 09:52:20 +0000 Subject: [PATCH 100/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/lambda.tf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a3fcf45c..6a8e0a99 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -11,9 +11,8 @@ module "s3lambda" { memory_size = var.mem_size description = "Lambda function for S3 event processing in SAET Triage API" - # subnet_ids = null - # security_group_ids = null - # vpc_id = null + subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] + security_group_ids = [aws_security_group.processor_lambda_security_group.id] environment_variables = { "ENVIRONMENT" = var.environment From f2187e7c744c85e71f70ba1095da7a485f52a72e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 10:50:44 +0000 Subject: [PATCH 101/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/security_group.tf | 7 +++++++ infrastructure/triage.tfvars | 1 + 2 files changed, 8 insertions(+) create mode 100644 infrastructure/stacks/triage/security_group.tf diff --git a/infrastructure/stacks/triage/security_group.tf b/infrastructure/stacks/triage/security_group.tf new file mode 100644 index 00000000..646fe4ca --- /dev/null +++ b/infrastructure/stacks/triage/security_group.tf @@ -0,0 +1,7 @@ +resource "aws_security_group" "processor_lambda_security_group" { + # checkov:skip=CKV2_AWS_5: False positive due to module reference + name = "${local.resource_prefix}-${var.processor_lambda_name}${local.workspace_suffix}-sg" + description = "Security group for processor lambda" + + vpc_id = data.aws_vpc.vpc.id +} diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index 97700499..b8b5267c 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -2,6 +2,7 @@ mem_size = 512 runtime = "python3.13" s3_key = "lambda_function.zip" +processor_lambda_name = "pathways-lambda-processor" #Rest API stage_name = "beta" From c6295624edb47be3e2c87614f4c78ec106a0ba13 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 11:14:17 +0000 Subject: [PATCH 102/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/data.tf | 6 ++++++ infrastructure/stacks/triage/variables.tf | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/infrastructure/stacks/triage/data.tf b/infrastructure/stacks/triage/data.tf index 4ef360ac..b4b568cd 100644 --- a/infrastructure/stacks/triage/data.tf +++ b/infrastructure/stacks/triage/data.tf @@ -21,3 +21,9 @@ data "aws_subnets" "private_subnets" { values = [var.vpc_private_subnet_cidr_range] } } + +data "aws_subnet" "private_subnets_details" { + for_each = toset(data.aws_subnets.private_subnets.ids) + id = each.value +} + diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 78c949f2..39b6a330 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -75,6 +75,10 @@ variable "vpc_private_subnet_cidr_range" { default = "24" } +variable "processor_lambda_name" { + description = "The name of the processor lambda function" +} + # REST API vars # variable "resources" { # type = list(object({ From 079112e3f9762f47b023d0f69e7b213838345015 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 11:23:27 +0000 Subject: [PATCH 103/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/lambda.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 6a8e0a99..b99913db 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -13,6 +13,7 @@ module "s3lambda" { subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] security_group_ids = [aws_security_group.processor_lambda_security_group.id] + vpc_id = data.aws_vpc.vpc.id environment_variables = { "ENVIRONMENT" = var.environment From 6551ec6c5986021305ce67b7ab3eeb71d43e02e7 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 12:07:14 +0000 Subject: [PATCH 104/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 39b6a330..dd8880ac 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -72,7 +72,7 @@ variable "mem_size" { variable "vpc_private_subnet_cidr_range" { description = "The CIDR range for the VPC private subnets" type = string - default = "24" + default = "21" } variable "processor_lambda_name" { From 0229d7ca45abd58d945b97f78cc5aa98dde80e27 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 12:17:49 +0000 Subject: [PATCH 105/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/lambda.tf | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index b99913db..ac244a86 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -26,6 +26,33 @@ module "s3lambda" { account_id = data.aws_caller_identity.current.account_id } +module "apiglambda" { + source = "../../modules/lambda" + aws_region = var.aws_region + function_name = "${local.resource_prefix}-apig-Lambda" + policy_jsons = [aws_iam_policy.ddb_access.policy] + handler = "api_gateway_configurator.handler" + s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id + runtime = var.runtime + s3_key = var.s3_key + memory_size = var.mem_size + description = "Lambda function for App gateway processing event in SAET Triage API" + + subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] + security_group_ids = [aws_security_group.processor_lambda_security_group.id] + vpc_id = data.aws_vpc.vpc.id + + environment_variables = { + "ENVIRONMENT" = var.environment + "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace + "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.s3_bucket_id + "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_arn + "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_arn + "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_arn + } + account_id = data.aws_caller_identity.current.account_id +} + # module "apiglambda" { # source = "../../modules/lambda" # aws_region = var.aws_region From 02b0add1cb885cea7fc732e72252376908551380 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 12:33:48 +0000 Subject: [PATCH 106/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/restapi.tf | 138 +++++++++++----------- infrastructure/stacks/triage/variables.tf | 42 ++++--- 2 files changed, 93 insertions(+), 87 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 6f76ace6..69a33de1 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,79 +1,79 @@ -# resource "aws_api_gateway_rest_api" "triage" { -# name = var.name -# } +resource "aws_api_gateway_rest_api" "triage" { + name = var.api_name +} -# resource "aws_api_gateway_resource" "resource" { -# for_each = { for w in var.resources : w.path_part => w } -# rest_api_id = aws_api_gateway_rest_api.triage.id -# parent_id = aws_api_gateway_rest_api.triage.root_resource_id -# path_part = each.value.path_part -# } +resource "aws_api_gateway_resource" "resource" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + parent_id = aws_api_gateway_rest_api.triage.root_resource_id + path_part = each.value.path_part +} -# resource "aws_api_gateway_method" "methods" { -# for_each = { for w in var.resources : w.path_part => w } -# rest_api_id = aws_api_gateway_rest_api.triage.id -# resource_id = aws_api_gateway_resource.resource[each.key].id -# http_method = each.value.method -# authorization = var.authorization -# } +resource "aws_api_gateway_method" "methods" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = each.value.method + authorization = var.authorization +} -# # Create method responses for the API methods -# resource "aws_api_gateway_method_response" "method_response" { -# for_each = { for w in var.resources : w.path_part => w } -# rest_api_id = aws_api_gateway_rest_api.triage.id -# resource_id = aws_api_gateway_resource.resource[each.key].id -# http_method = aws_api_gateway_method.methods[each.key].http_method -# status_code = "200" -# } +# Create method responses for the API methods +resource "aws_api_gateway_method_response" "method_response" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + status_code = "200" +} -# resource "aws_api_gateway_integration" "integrations" { -# for_each = { for w in var.resources : w.path_part => w } -# rest_api_id = aws_api_gateway_rest_api.triage.id -# resource_id = aws_api_gateway_resource.resource[each.key].id -# http_method = aws_api_gateway_method.methods[each.key].http_method -# integration_http_method = var.int_http_method -# type = var.type -# uri = each.value.lambda_invoke_arn -# } +resource "aws_api_gateway_integration" "integrations" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + integration_http_method = var.int_http_method + type = var.type + uri = each.value.lambda_invoke_arn +} -# # Create integration responses for the API methods -# resource "aws_api_gateway_integration_response" "integration_response" { -# for_each = { for w in var.resources : w.path_part => w } -# rest_api_id = aws_api_gateway_rest_api.triage.id -# resource_id = aws_api_gateway_resource.resource[each.key].id -# http_method = aws_api_gateway_method.methods[each.key].http_method -# status_code = aws_api_gateway_method_response.method_response[each.key].status_code -# selection_pattern = "" -# } +# Create integration responses for the API methods +resource "aws_api_gateway_integration_response" "integration_response" { + for_each = { for w in var.resources : w.path_part => w } + rest_api_id = aws_api_gateway_rest_api.triage.id + resource_id = aws_api_gateway_resource.resource[each.key].id + http_method = aws_api_gateway_method.methods[each.key].http_method + status_code = aws_api_gateway_method_response.method_response[each.key].status_code + selection_pattern = "" +} -# # Create a deployment to publish the API -# resource "aws_api_gateway_deployment" "deployment" { -# rest_api_id = aws_api_gateway_rest_api.triage.id +# Create a deployment to publish the API +resource "aws_api_gateway_deployment" "deployment" { + rest_api_id = aws_api_gateway_rest_api.triage.id -# depends_on = [ -# aws_api_gateway_integration.integrations, -# aws_api_gateway_integration_response.integration_response, -# aws_api_gateway_method.methods -# ] + depends_on = [ + aws_api_gateway_integration.integrations, + aws_api_gateway_integration_response.integration_response, + aws_api_gateway_method.methods + ] -# # Force redeployment when any API resources change -# triggers = { -# redeployment = sha1(jsonencode({ -# resources = aws_api_gateway_resource.resource -# methods = aws_api_gateway_method.methods -# integrations = aws_api_gateway_integration.integrations -# integration_responses = aws_api_gateway_integration_response.integration_response -# })) -# } + # Force redeployment when any API resources change + triggers = { + redeployment = sha1(jsonencode({ + resources = aws_api_gateway_resource.resource + methods = aws_api_gateway_method.methods + integrations = aws_api_gateway_integration.integrations + integration_responses = aws_api_gateway_integration_response.integration_response + })) + } -# lifecycle { -# create_before_destroy = true -# } -# } + lifecycle { + create_before_destroy = true + } +} -# # Create a stage (environment) for the API -# resource "aws_api_gateway_stage" "stage" { -# deployment_id = aws_api_gateway_deployment.deployment.id -# rest_api_id = aws_api_gateway_rest_api.triage.id -# stage_name = var.stage_name -# } +# Create a stage (environment) for the API +resource "aws_api_gateway_stage" "stage" { + deployment_id = aws_api_gateway_deployment.deployment.id + rest_api_id = aws_api_gateway_rest_api.triage.id + stage_name = var.stage_name +} diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index dd8880ac..51322402 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -80,28 +80,34 @@ variable "processor_lambda_name" { } # REST API vars -# variable "resources" { -# type = list(object({ -# path_part = string -# method = string -# lambda_invoke_arn = string -# })) -# } +variable "api_name" { + type = string +} +variable "resources" { + type = list(object({ + path_part = string + method = string + lambda_invoke_arn = string + })) +} -# variable "type" { -# type = string -# default = "AWS_PROXY" -# } +variable "type" { + type = string + default = "AWS_PROXY" +} -# variable "int_http_method" { -# type = string -# default = "POST" -# } +variable "int_http_method" { + type = string + default = "POST" +} -# variable "stage_name" { -# type = string -# } +variable "stage_name" { + type = string +} +variable "authorization" { + type = string +} # Trigger vars # variable "statement_id" { From 405f3e5d02fb3e72851fa052de21a8afaf5f61e6 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 12:40:37 +0000 Subject: [PATCH 107/181] Added extra information into the pre commit file --- infrastructure/triage.tfvars | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index b8b5267c..c6018820 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -6,3 +6,4 @@ processor_lambda_name = "pathways-lambda-processor" #Rest API stage_name = "beta" +api_name = "triage_api" From c128cd185c6e169b31b7d5ac071ef250cfabdadf Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 12:57:23 +0000 Subject: [PATCH 108/181] Added extra information into the pre commit file --- infrastructure/triage.tfvars | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index c6018820..725e23e8 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -7,3 +7,4 @@ processor_lambda_name = "pathways-lambda-processor" #Rest API stage_name = "beta" api_name = "triage_api" +authorization = "NONE" From 1f4e7785e811252a788d92b2edd13e80f46aab1b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 13:13:03 +0000 Subject: [PATCH 109/181] Added extra information into the pre commit file --- infrastructure/stacks/triage/variables.tf | 6 ++++++ infrastructure/triage.tfvars | 1 + 2 files changed, 7 insertions(+) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 51322402..4dfefa5d 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -89,6 +89,12 @@ variable "resources" { method = string lambda_invoke_arn = string })) + default = [{ + path_part = "triage" + method = "POST" + lambda_invoke_arn = module.apiglambda.lambda_function_arn + } + ] } variable "type" { diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index 725e23e8..ffa67283 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -8,3 +8,4 @@ processor_lambda_name = "pathways-lambda-processor" stage_name = "beta" api_name = "triage_api" authorization = "NONE" + From eafa557acc4290b5570566345c6da22769217ca2 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:05:52 +0000 Subject: [PATCH 110/181] Adding rest api config --- infrastructure/stacks/triage/locals.tf | 37 +++++++++++++++++++++++ infrastructure/stacks/triage/restapi.tf | 10 +++--- infrastructure/stacks/triage/variables.tf | 6 ---- 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 infrastructure/stacks/triage/locals.tf diff --git a/infrastructure/stacks/triage/locals.tf b/infrastructure/stacks/triage/locals.tf new file mode 100644 index 00000000..5570878c --- /dev/null +++ b/infrastructure/stacks/triage/locals.tf @@ -0,0 +1,37 @@ +locals { + account_id = data.aws_caller_identity.current.id + workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" + project_prefix = "${var.project}-${var.environment}" + resource_prefix = "${local.project_prefix}-${var.stack_name}" + account_prefix = "${var.repo_name}-${var.environment}" + s3_logging_bucket = "${local.account_prefix}-${var.s3_logging_bucket_name}" + + # Deploy certain resources (e.g., databases, backup SSM) only in default Terraform workspace. + is_primary_environment = terraform.workspace == "default" + rds_environments = var.environment == "dev" || var.environment == "test" || var.environment == "int" + + domain_cross_account_role = "${var.repo_name}-mgmt-domain-name-cross-account-access" + + env_sso_roles = [ + for role in var.sso_roles : "arn:aws:iam::${local.account_id}:role/aws-reserved/sso.amazonaws.com/${var.aws_region}/${role}" + ] + + kms_aliases = { + sqs = "alias/${local.project_prefix}-sqs-kms" + secrets_manager = "alias/${local.project_prefix}-secrets-manager-kms" + ssm = "alias/${local.project_prefix}-ssm-kms" + dms = "alias/${local.project_prefix}-dms-kms" + dynamodb = "alias/${local.project_prefix}-dynamodb-kms" + s3 = "alias/${local.project_prefix}-s3-kms" + rds = "alias/${local.project_prefix}-rds-kms" + opensearch = "alias/${local.project_prefix}-opensearch-kms" + } + + api_resources = length(var.resources) > 0 ? var.resources : [ + { + path_part = "triage" + method = "POST" + lambda_invoke_arn = module.apiglambda.lambda_function_arn + } + ] +} diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 69a33de1..d92465bb 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -3,14 +3,14 @@ resource "aws_api_gateway_rest_api" "triage" { } resource "aws_api_gateway_resource" "resource" { - for_each = { for w in var.resources : w.path_part => w } + for_each = { for w in local.api_resources : w.path_part => w } rest_api_id = aws_api_gateway_rest_api.triage.id parent_id = aws_api_gateway_rest_api.triage.root_resource_id path_part = each.value.path_part } resource "aws_api_gateway_method" "methods" { - for_each = { for w in var.resources : w.path_part => w } + for_each = { for w in local.api_resources : w.path_part => w } rest_api_id = aws_api_gateway_rest_api.triage.id resource_id = aws_api_gateway_resource.resource[each.key].id http_method = each.value.method @@ -19,7 +19,7 @@ resource "aws_api_gateway_method" "methods" { # Create method responses for the API methods resource "aws_api_gateway_method_response" "method_response" { - for_each = { for w in var.resources : w.path_part => w } + for_each = { for w in local.api_resources : w.path_part => w } rest_api_id = aws_api_gateway_rest_api.triage.id resource_id = aws_api_gateway_resource.resource[each.key].id http_method = aws_api_gateway_method.methods[each.key].http_method @@ -27,7 +27,7 @@ resource "aws_api_gateway_method_response" "method_response" { } resource "aws_api_gateway_integration" "integrations" { - for_each = { for w in var.resources : w.path_part => w } + for_each = { for w in local.api_resources : w.path_part => w } rest_api_id = aws_api_gateway_rest_api.triage.id resource_id = aws_api_gateway_resource.resource[each.key].id http_method = aws_api_gateway_method.methods[each.key].http_method @@ -38,7 +38,7 @@ resource "aws_api_gateway_integration" "integrations" { # Create integration responses for the API methods resource "aws_api_gateway_integration_response" "integration_response" { - for_each = { for w in var.resources : w.path_part => w } + for_each = { for w in local.api_resources : w.path_part => w } rest_api_id = aws_api_gateway_rest_api.triage.id resource_id = aws_api_gateway_resource.resource[each.key].id http_method = aws_api_gateway_method.methods[each.key].http_method diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 4dfefa5d..51322402 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -89,12 +89,6 @@ variable "resources" { method = string lambda_invoke_arn = string })) - default = [{ - path_part = "triage" - method = "POST" - lambda_invoke_arn = module.apiglambda.lambda_function_arn - } - ] } variable "type" { From 1e6b5aaf77c9585fd3367b0ebaeb200a58efa2e0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:20:39 +0000 Subject: [PATCH 111/181] Adding rest api config --- infrastructure/common/locals.tf | 1 + infrastructure/stacks/triage/locals.tf | 37 ------------------------- infrastructure/stacks/triage/restapi.tf | 10 +++++++ 3 files changed, 11 insertions(+), 37 deletions(-) delete mode 100644 infrastructure/stacks/triage/locals.tf diff --git a/infrastructure/common/locals.tf b/infrastructure/common/locals.tf index 53d683bc..6d6bf42e 100644 --- a/infrastructure/common/locals.tf +++ b/infrastructure/common/locals.tf @@ -27,3 +27,4 @@ locals { opensearch = "alias/${local.project_prefix}-opensearch-kms" } } + diff --git a/infrastructure/stacks/triage/locals.tf b/infrastructure/stacks/triage/locals.tf deleted file mode 100644 index 5570878c..00000000 --- a/infrastructure/stacks/triage/locals.tf +++ /dev/null @@ -1,37 +0,0 @@ -locals { - account_id = data.aws_caller_identity.current.id - workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" - project_prefix = "${var.project}-${var.environment}" - resource_prefix = "${local.project_prefix}-${var.stack_name}" - account_prefix = "${var.repo_name}-${var.environment}" - s3_logging_bucket = "${local.account_prefix}-${var.s3_logging_bucket_name}" - - # Deploy certain resources (e.g., databases, backup SSM) only in default Terraform workspace. - is_primary_environment = terraform.workspace == "default" - rds_environments = var.environment == "dev" || var.environment == "test" || var.environment == "int" - - domain_cross_account_role = "${var.repo_name}-mgmt-domain-name-cross-account-access" - - env_sso_roles = [ - for role in var.sso_roles : "arn:aws:iam::${local.account_id}:role/aws-reserved/sso.amazonaws.com/${var.aws_region}/${role}" - ] - - kms_aliases = { - sqs = "alias/${local.project_prefix}-sqs-kms" - secrets_manager = "alias/${local.project_prefix}-secrets-manager-kms" - ssm = "alias/${local.project_prefix}-ssm-kms" - dms = "alias/${local.project_prefix}-dms-kms" - dynamodb = "alias/${local.project_prefix}-dynamodb-kms" - s3 = "alias/${local.project_prefix}-s3-kms" - rds = "alias/${local.project_prefix}-rds-kms" - opensearch = "alias/${local.project_prefix}-opensearch-kms" - } - - api_resources = length(var.resources) > 0 ? var.resources : [ - { - path_part = "triage" - method = "POST" - lambda_invoke_arn = module.apiglambda.lambda_function_arn - } - ] -} diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index d92465bb..72a1ef27 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,3 +1,13 @@ +locals { + api_resources = length(var.resources) > 0 ? var.resources : [ + { + path_part = "triage" + method = "POST" + lambda_invoke_arn = module.apiglambda.lambda_function_arn + } + ] +} + resource "aws_api_gateway_rest_api" "triage" { name = var.api_name } From 0565642b3fea7cca99fdd7ccf97d9f67453d32f0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:28:19 +0000 Subject: [PATCH 112/181] Adding rest api config --- infrastructure/stacks/triage/restapi.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 72a1ef27..e313b467 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,5 +1,5 @@ locals { - api_resources = length(var.resources) > 0 ? var.resources : [ + api_resources = [ { path_part = "triage" method = "POST" From 3e15a0bb789bed4371fe8ea5f15a8a05e69c3a4f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:29:39 +0000 Subject: [PATCH 113/181] Adding tfstate to git ignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 1f09ff50..1531a8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ tests/integration/release/*.zip package jmeter.log tmp-postman-env.json + +# Terraform +*.tfstate +*.tfstate.* +**/.terraform/* +**/.terraform.lock.hcl From 745ca8e02d2a1fdd3e9a929e71e5d009774c9c06 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:35:54 +0000 Subject: [PATCH 114/181] Adding rest api config --- infrastructure/stacks/triage/variables.tf | 7 ------- 1 file changed, 7 deletions(-) diff --git a/infrastructure/stacks/triage/variables.tf b/infrastructure/stacks/triage/variables.tf index 51322402..f6541813 100644 --- a/infrastructure/stacks/triage/variables.tf +++ b/infrastructure/stacks/triage/variables.tf @@ -83,13 +83,6 @@ variable "processor_lambda_name" { variable "api_name" { type = string } -variable "resources" { - type = list(object({ - path_part = string - method = string - lambda_invoke_arn = string - })) -} variable "type" { type = string From 2706ba0f51790ae696f1bf85a45331d8c6e66f51 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:42:29 +0000 Subject: [PATCH 115/181] Adding rest api config --- infrastructure/stacks/triage/restapi.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index e313b467..2b772c11 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -3,7 +3,7 @@ locals { { path_part = "triage" method = "POST" - lambda_invoke_arn = module.apiglambda.lambda_function_arn + lambda_invoke_arn = module.apiglambda.lambda_function_invoke_arn } ] } From c9da8bf040ea15494d0e54c3888a3ac1d3b2b399 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 14:48:12 +0000 Subject: [PATCH 116/181] Adding trigger config --- infrastructure/stacks/triage/trigger.tf | 53 +++++++++++++------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/infrastructure/stacks/triage/trigger.tf b/infrastructure/stacks/triage/trigger.tf index 1cc36033..327aa0f0 100644 --- a/infrastructure/stacks/triage/trigger.tf +++ b/infrastructure/stacks/triage/trigger.tf @@ -1,28 +1,29 @@ -# resource "aws_lambda_permission" "allows3" { -# count = var.bucket_arn != null ? 1 : 0 -# statement_id = var.statement_id -# action = var.action -# function_name = var.lambda_name -# principal = var.principal -# source_arn = var.bucket_arn -# } +# S3 Lambda trigger for s3lambda +resource "aws_lambda_permission" "allows3" { + statement_id = "AllowExecutionFromS3" + action = "lambda:InvokeFunction" + function_name = module.s3lambda.lambda_function_name + principal = "s3.amazonaws.com" + source_arn = module.pathway_artifact_bucket.s3_bucket_arn +} -# resource "aws_s3_bucket_notification" "bucket_notification" { -# count = var.bucket_name != null ? 1 : 0 -# bucket = var.bucket_name -# lambda_function { -# lambda_function_arn = var.lambda_arn -# events = var.events -# #filter_prefix = var.filter_prefix -# } -# depends_on = [aws_lambda_permission.allows3] -# } +resource "aws_s3_bucket_notification" "bucket_notification" { + bucket = module.pathway_artifact_bucket.s3_bucket_id -# resource "aws_lambda_permission" "allowapig" { -# count = var.api_gateway_source_arn != null ? 1 : 0 -# statement_id = var.statement_id -# action = var.action -# function_name = var.lambda_name -# principal = var.principal -# source_arn = var.api_gateway_source_arn -# } + lambda_function { + lambda_function_arn = module.s3lambda.lambda_function_arn + events = ["s3:ObjectCreated:*"] + #filter_prefix = "uploads/" + } + + depends_on = [aws_lambda_permission.allows3] +} + +# API Gateway Lambda trigger for apiglambda +resource "aws_lambda_permission" "allowapig" { + statement_id = "AllowExecutionFromAPIGateway" + action = "lambda:InvokeFunction" + function_name = module.apiglambda.lambda_function_name + principal = "apigateway.amazonaws.com" + source_arn = "${aws_api_gateway_rest_api.triage.execution_arn}/*/*" +} From e0796ccacd5769bfa4bb042921ff57d9b8076b21 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 15:38:00 +0000 Subject: [PATCH 117/181] Adding trigger config with alias --- infrastructure/stacks/triage/trigger.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/triage/trigger.tf b/infrastructure/stacks/triage/trigger.tf index 327aa0f0..fce000ab 100644 --- a/infrastructure/stacks/triage/trigger.tf +++ b/infrastructure/stacks/triage/trigger.tf @@ -26,4 +26,5 @@ resource "aws_lambda_permission" "allowapig" { function_name = module.apiglambda.lambda_function_name principal = "apigateway.amazonaws.com" source_arn = "${aws_api_gateway_rest_api.triage.execution_arn}/*/*" + qualifier = "live" } From d589295c08191925cad9bac99dbaf2ad95127820 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 15:51:10 +0000 Subject: [PATCH 118/181] Adding trigger config with alias --- infrastructure/stacks/triage/lambda.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index ac244a86..9b2093d9 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -53,6 +53,12 @@ module "apiglambda" { account_id = data.aws_caller_identity.current.account_id } +resource "aws_lambda_alias" "apiglambda_live" { + name = "live" + function_name = module.apiglambda.lambda_function_name + function_version = module.apiglambda.lambda_function_version +} + # module "apiglambda" { # source = "../../modules/lambda" # aws_region = var.aws_region From cb3ca9c2face3324cf9d852a2b7467c2935acb9a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 11 Feb 2026 15:56:46 +0000 Subject: [PATCH 119/181] Adding trigger config with alias --- infrastructure/modules/lambda/outputs.tf | 5 +++++ infrastructure/stacks/triage/lambda.tf | 15 --------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/infrastructure/modules/lambda/outputs.tf b/infrastructure/modules/lambda/outputs.tf index 4c272549..d94ba89e 100644 --- a/infrastructure/modules/lambda/outputs.tf +++ b/infrastructure/modules/lambda/outputs.tf @@ -27,3 +27,8 @@ output "lambda_role_arn" { output "lambda_cloudwatch_log_group_name" { value = module.lambda.lambda_cloudwatch_log_group_name } + +output "lambda_function_version" { + description = "Latest published version of Lambda Function" + value = module.lambda.lambda_function_version +} diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 9b2093d9..a7e740e2 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -58,18 +58,3 @@ resource "aws_lambda_alias" "apiglambda_live" { function_name = module.apiglambda.lambda_function_name function_version = module.apiglambda.lambda_function_version } - -# module "apiglambda" { -# source = "../../modules/lambda" -# aws_region = var.aws_region -# function_name = "APIG-lambda-JACK-TEST" -# lambda_role = module.iam.lambda_role_arn -# handler = "api_gateway_configurator.handler" -# bucket_name = module.SAETBucket.bucket_name -# runtime = var.runtime -# s3_key = var.s3_key -# mem_size = var.mem_size -# body_map_table = module.bodymap.table_name -# starting_node_table = module.startingnode.table_name -# triage_node_table = module.triagenode.table_name -# } From 93120ae700ef7bd611208c27e65d30ea8b99bdc0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 10:09:37 +0000 Subject: [PATCH 120/181] Pipeline test --- infrastructure/stacks/triage/dynamodb.tf | 6 +++--- infrastructure/stacks/triage/lambda.tf | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index 34fc32b7..3b01ee58 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -1,6 +1,6 @@ module "starting_coords" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-StartingCoords" + table_name = "${local.resource_prefix}-StartingCoords-jack" hash_key = "Skillset" range_key = "GenderAgeParty" @@ -12,7 +12,7 @@ module "starting_coords" { module "triage_nodes" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-TriageNodes" + table_name = "${local.resource_prefix}-TriageNodes-jack" hash_key = "Coordinate" attributes = [{ @@ -23,7 +23,7 @@ module "triage_nodes" { module "bodymaps" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-BodyMaps" + table_name = "${local.resource_prefix}-BodyMaps-jack" hash_key = "id" attributes = [{ diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a7e740e2..935a1eb0 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -2,7 +2,7 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-s3-Lambda" + function_name = "${local.resource_prefix}-s3-Lambda-jack" policy_jsons = [aws_iam_policy.s3_access.policy] handler = "s3_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id @@ -29,7 +29,7 @@ module "s3lambda" { module "apiglambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-apig-Lambda" + function_name = "${local.resource_prefix}-apig-Lambda-jack" policy_jsons = [aws_iam_policy.ddb_access.policy] handler = "api_gateway_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id From bdfe005cc065eaeb30e1db6db3428e5bec245089 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 12:43:08 +0000 Subject: [PATCH 121/181] Pipeline test --- infrastructure/stacks/triage/dynamodb.tf | 6 +++--- infrastructure/stacks/triage/lambda.tf | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index 3b01ee58..34fc32b7 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -1,6 +1,6 @@ module "starting_coords" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-StartingCoords-jack" + table_name = "${local.resource_prefix}-StartingCoords" hash_key = "Skillset" range_key = "GenderAgeParty" @@ -12,7 +12,7 @@ module "starting_coords" { module "triage_nodes" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-TriageNodes-jack" + table_name = "${local.resource_prefix}-TriageNodes" hash_key = "Coordinate" attributes = [{ @@ -23,7 +23,7 @@ module "triage_nodes" { module "bodymaps" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-BodyMaps-jack" + table_name = "${local.resource_prefix}-BodyMaps" hash_key = "id" attributes = [{ diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 935a1eb0..a7e740e2 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -2,7 +2,7 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-s3-Lambda-jack" + function_name = "${local.resource_prefix}-s3-Lambda" policy_jsons = [aws_iam_policy.s3_access.policy] handler = "s3_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id @@ -29,7 +29,7 @@ module "s3lambda" { module "apiglambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-apig-Lambda-jack" + function_name = "${local.resource_prefix}-apig-Lambda" policy_jsons = [aws_iam_policy.ddb_access.policy] handler = "api_gateway_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id From 837da4c7d65b09cc4823f81fc31db8f757a93c00 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 14:48:17 +0000 Subject: [PATCH 122/181] Pipeline test with Rajiv --- infrastructure/stacks/triage/dynamodb.tf | 6 +++--- infrastructure/stacks/triage/lambda.tf | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index 34fc32b7..3b01ee58 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -1,6 +1,6 @@ module "starting_coords" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-StartingCoords" + table_name = "${local.resource_prefix}-StartingCoords-jack" hash_key = "Skillset" range_key = "GenderAgeParty" @@ -12,7 +12,7 @@ module "starting_coords" { module "triage_nodes" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-TriageNodes" + table_name = "${local.resource_prefix}-TriageNodes-jack" hash_key = "Coordinate" attributes = [{ @@ -23,7 +23,7 @@ module "triage_nodes" { module "bodymaps" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-BodyMaps" + table_name = "${local.resource_prefix}-BodyMaps-jack" hash_key = "id" attributes = [{ diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a7e740e2..935a1eb0 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -2,7 +2,7 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-s3-Lambda" + function_name = "${local.resource_prefix}-s3-Lambda-jack" policy_jsons = [aws_iam_policy.s3_access.policy] handler = "s3_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id @@ -29,7 +29,7 @@ module "s3lambda" { module "apiglambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-apig-Lambda" + function_name = "${local.resource_prefix}-apig-Lambda-jack" policy_jsons = [aws_iam_policy.ddb_access.policy] handler = "api_gateway_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id From 604515fe9c81afd6f79f0a5dbf33c4bfc408470f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 14:58:38 +0000 Subject: [PATCH 123/181] Pipeline test with Rajiv --- infrastructure/stacks/triage/dynamodb.tf | 6 +++--- infrastructure/stacks/triage/lambda.tf | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/triage/dynamodb.tf b/infrastructure/stacks/triage/dynamodb.tf index 3b01ee58..34fc32b7 100644 --- a/infrastructure/stacks/triage/dynamodb.tf +++ b/infrastructure/stacks/triage/dynamodb.tf @@ -1,6 +1,6 @@ module "starting_coords" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-StartingCoords-jack" + table_name = "${local.resource_prefix}-StartingCoords" hash_key = "Skillset" range_key = "GenderAgeParty" @@ -12,7 +12,7 @@ module "starting_coords" { module "triage_nodes" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-TriageNodes-jack" + table_name = "${local.resource_prefix}-TriageNodes" hash_key = "Coordinate" attributes = [{ @@ -23,7 +23,7 @@ module "triage_nodes" { module "bodymaps" { source = "../../modules/dynamodb" - table_name = "${local.resource_prefix}-BodyMaps-jack" + table_name = "${local.resource_prefix}-BodyMaps" hash_key = "id" attributes = [{ diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 935a1eb0..a7e740e2 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -2,7 +2,7 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-s3-Lambda-jack" + function_name = "${local.resource_prefix}-s3-Lambda" policy_jsons = [aws_iam_policy.s3_access.policy] handler = "s3_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id @@ -29,7 +29,7 @@ module "s3lambda" { module "apiglambda" { source = "../../modules/lambda" aws_region = var.aws_region - function_name = "${local.resource_prefix}-apig-Lambda-jack" + function_name = "${local.resource_prefix}-apig-Lambda" policy_jsons = [aws_iam_policy.ddb_access.policy] handler = "api_gateway_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id From 0775b106ccf6c01bc76b450370819bfe86cba5df Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 15:45:46 +0000 Subject: [PATCH 124/181] testing api changes --- infrastructure/stacks/triage/restapi.tf | 60 +++++++++++-------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 2b772c11..cfd15d61 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,11 +1,5 @@ locals { - api_resources = [ - { - path_part = "triage" - method = "POST" - lambda_invoke_arn = module.apiglambda.lambda_function_invoke_arn - } - ] + path_parts = ["FHIR", "R4", "triage"] } resource "aws_api_gateway_rest_api" "triage" { @@ -13,46 +7,44 @@ resource "aws_api_gateway_rest_api" "triage" { } resource "aws_api_gateway_resource" "resource" { - for_each = { for w in local.api_resources : w.path_part => w } + count = length(local.path_parts) rest_api_id = aws_api_gateway_rest_api.triage.id - parent_id = aws_api_gateway_rest_api.triage.root_resource_id - path_part = each.value.path_part + parent_id = count.index == 0 + ? aws_api_gateway_rest_api.triage.root_resource_id + : aws_api_gateway_resource.resource[count.index - 1].id + path_part = local.path_parts[count.index] } -resource "aws_api_gateway_method" "methods" { - for_each = { for w in local.api_resources : w.path_part => w } +resource "aws_api_gateway_method" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = each.value.method + resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + http_method = "POST" authorization = var.authorization } # Create method responses for the API methods -resource "aws_api_gateway_method_response" "method_response" { - for_each = { for w in local.api_resources : w.path_part => w } +resource "aws_api_gateway_method_response" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method + resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + http_method = aws_api_gateway_method.triage.http_method status_code = "200" } -resource "aws_api_gateway_integration" "integrations" { - for_each = { for w in local.api_resources : w.path_part => w } +resource "aws_api_gateway_integration" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method + resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + http_method = aws_api_gateway_method.triage.http_method integration_http_method = var.int_http_method type = var.type - uri = each.value.lambda_invoke_arn + uri = module.apiglambda.lambda_function_invoke_arn } # Create integration responses for the API methods -resource "aws_api_gateway_integration_response" "integration_response" { - for_each = { for w in local.api_resources : w.path_part => w } +resource "aws_api_gateway_integration_response" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[each.key].id - http_method = aws_api_gateway_method.methods[each.key].http_method - status_code = aws_api_gateway_method_response.method_response[each.key].status_code + resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + http_method = aws_api_gateway_method.triage.http_method + status_code = aws_api_gateway_method_response.triage.status_code selection_pattern = "" } @@ -61,18 +53,18 @@ resource "aws_api_gateway_deployment" "deployment" { rest_api_id = aws_api_gateway_rest_api.triage.id depends_on = [ - aws_api_gateway_integration.integrations, - aws_api_gateway_integration_response.integration_response, - aws_api_gateway_method.methods + aws_api_gateway_integration.triage, + aws_api_gateway_integration_response.triage, + aws_api_gateway_method.triage ] # Force redeployment when any API resources change triggers = { redeployment = sha1(jsonencode({ resources = aws_api_gateway_resource.resource - methods = aws_api_gateway_method.methods - integrations = aws_api_gateway_integration.integrations - integration_responses = aws_api_gateway_integration_response.integration_response + methods = aws_api_gateway_method.triage + integrations = aws_api_gateway_integration.triage + integration_responses = aws_api_gateway_integration_response.triage })) } From a5d5193a89bfca97fbb1a7db8259e0bfa454648c Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 12 Feb 2026 15:49:20 +0000 Subject: [PATCH 125/181] testing api changes --- infrastructure/stacks/triage/restapi.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index cfd15d61..a0a57c4d 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -9,9 +9,7 @@ resource "aws_api_gateway_rest_api" "triage" { resource "aws_api_gateway_resource" "resource" { count = length(local.path_parts) rest_api_id = aws_api_gateway_rest_api.triage.id - parent_id = count.index == 0 - ? aws_api_gateway_rest_api.triage.root_resource_id - : aws_api_gateway_resource.resource[count.index - 1].id + parent_id = count.index == 0 ? aws_api_gateway_rest_api.triage.root_resource_id : aws_api_gateway_resource.resource[count.index - 1].id path_part = local.path_parts[count.index] } From 9e382dab773518e1b60aa0522a7cb51da7781938 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 13 Feb 2026 11:16:28 +0000 Subject: [PATCH 126/181] testing api changes --- scripts/docker/docker.lib.sh | 17 +++++++++++++++-- scripts/terraform/terraform.sh | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/scripts/docker/docker.lib.sh b/scripts/docker/docker.lib.sh index 9276cf3b..309a88c6 100644 --- a/scripts/docker/docker.lib.sh +++ b/scripts/docker/docker.lib.sh @@ -204,19 +204,32 @@ function docker-get-image-version-and-pull() { local digest="$(echo "$version" | sed 's/^.*@//')" DOCKER_CMD=$(_set_docker_cmd) + local platform="${DOCKER_PLATFORM:-}" + if [ -z "$platform" ]; then + case "$(uname -m)" in + x86_64|amd64) platform="linux/amd64" ;; + aarch64|arm64) platform="linux/arm64" ;; + *) platform="" ;; + esac + fi + local platform_args=() + if [ -n "$platform" ]; then + platform_args=(--platform "$platform") + fi + # Check if the image exists locally already if ! $DOCKER_CMD images | awk '{ print $1 ":" $2 }' | grep -q "^${name}:${tag}$"; then if [ "$digest" != "latest" ]; then # Pull image by the digest sha256 and tag it $DOCKER_CMD pull \ - --platform linux/amd64 \ + "${platform_args[@]}" \ "${name}@${digest}" \ > /dev/null 2>&1 || true $DOCKER_CMD tag "${name}@${digest}" "${name}:${tag}" else # Pull the latest image $DOCKER_CMD pull \ - --platform linux/amd64 \ + "${platform_args[@]}" \ "${name}:latest" \ > /dev/null 2>&1 || true fi diff --git a/scripts/terraform/terraform.sh b/scripts/terraform/terraform.sh index f388c9a9..6e0a64f3 100755 --- a/scripts/terraform/terraform.sh +++ b/scripts/terraform/terraform.sh @@ -49,10 +49,23 @@ function run-terraform-in-docker() { # shellcheck disable=SC1091 source ./scripts/docker/docker.lib.sh + local platform="${DOCKER_PLATFORM:-}" + if [ -z "$platform" ]; then + case "$(uname -m)" in + x86_64|amd64) platform="linux/amd64" ;; + aarch64|arm64) platform="linux/arm64" ;; + *) platform="" ;; + esac + fi + local platform_args=() + if [ -n "$platform" ]; then + platform_args=(--platform "$platform") + fi + # shellcheck disable=SC2155 local image=$(name=hashicorp/terraform docker-get-image-version-and-pull) # shellcheck disable=SC2086 - docker run --rm --platform linux/amd64 \ + docker run --rm "${platform_args[@]}" \ --volume "$PWD":/workdir \ --workdir /workdir \ "$image" \ From 144c3cc64a617e84fb7df8fee74650d79e7bde4f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 13 Feb 2026 12:11:12 +0000 Subject: [PATCH 127/181] testing api changes --- infrastructure/stacks/triage/restapi.tf | 37 ++++++++++++++++--------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index a0a57c4d..c3a629b7 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,21 +1,28 @@ -locals { - path_parts = ["FHIR", "R4", "triage"] -} - resource "aws_api_gateway_rest_api" "triage" { name = var.api_name } -resource "aws_api_gateway_resource" "resource" { - count = length(local.path_parts) +resource "aws_api_gateway_resource" "fhir" { + rest_api_id = aws_api_gateway_rest_api.triage.id + parent_id = aws_api_gateway_rest_api.triage.root_resource_id + path_part = "FHIR" +} + +resource "aws_api_gateway_resource" "r4" { + rest_api_id = aws_api_gateway_rest_api.triage.id + parent_id = aws_api_gateway_resource.fhir.id + path_part = "R4" +} + +resource "aws_api_gateway_resource" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - parent_id = count.index == 0 ? aws_api_gateway_rest_api.triage.root_resource_id : aws_api_gateway_resource.resource[count.index - 1].id - path_part = local.path_parts[count.index] + parent_id = aws_api_gateway_resource.r4.id + path_part = "triage" } resource "aws_api_gateway_method" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + resource_id = aws_api_gateway_resource.triage.id http_method = "POST" authorization = var.authorization } @@ -23,14 +30,14 @@ resource "aws_api_gateway_method" "triage" { # Create method responses for the API methods resource "aws_api_gateway_method_response" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + resource_id = aws_api_gateway_resource.triage.id http_method = aws_api_gateway_method.triage.http_method status_code = "200" } resource "aws_api_gateway_integration" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + resource_id = aws_api_gateway_resource.triage.id http_method = aws_api_gateway_method.triage.http_method integration_http_method = var.int_http_method type = var.type @@ -40,7 +47,7 @@ resource "aws_api_gateway_integration" "triage" { # Create integration responses for the API methods resource "aws_api_gateway_integration_response" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id - resource_id = aws_api_gateway_resource.resource[length(local.path_parts) - 1].id + resource_id = aws_api_gateway_resource.triage.id http_method = aws_api_gateway_method.triage.http_method status_code = aws_api_gateway_method_response.triage.status_code selection_pattern = "" @@ -59,7 +66,11 @@ resource "aws_api_gateway_deployment" "deployment" { # Force redeployment when any API resources change triggers = { redeployment = sha1(jsonencode({ - resources = aws_api_gateway_resource.resource + resources = { + fhir = aws_api_gateway_resource.fhir + r4 = aws_api_gateway_resource.r4 + triage = aws_api_gateway_resource.triage + } methods = aws_api_gateway_method.triage integrations = aws_api_gateway_integration.triage integration_responses = aws_api_gateway_integration_response.triage From 2d4648d171dd5c5cc2e741b64b76884f64d52de4 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 13 Feb 2026 12:19:28 +0000 Subject: [PATCH 128/181] testing api changes --- infrastructure/stacks/triage/restapi.tf | 13 +++++++++---- infrastructure/stacks/triage/trigger.tf | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index c3a629b7..5dd005d2 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -1,3 +1,8 @@ +locals { + api_method = "POST" + path_parts = ["FHIR", "R4", "triage"] +} + resource "aws_api_gateway_rest_api" "triage" { name = var.api_name } @@ -5,25 +10,25 @@ resource "aws_api_gateway_rest_api" "triage" { resource "aws_api_gateway_resource" "fhir" { rest_api_id = aws_api_gateway_rest_api.triage.id parent_id = aws_api_gateway_rest_api.triage.root_resource_id - path_part = "FHIR" + path_part = local.path_parts[0] } resource "aws_api_gateway_resource" "r4" { rest_api_id = aws_api_gateway_rest_api.triage.id parent_id = aws_api_gateway_resource.fhir.id - path_part = "R4" + path_part = local.path_parts[1] } resource "aws_api_gateway_resource" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id parent_id = aws_api_gateway_resource.r4.id - path_part = "triage" + path_part = local.path_parts[2] } resource "aws_api_gateway_method" "triage" { rest_api_id = aws_api_gateway_rest_api.triage.id resource_id = aws_api_gateway_resource.triage.id - http_method = "POST" + http_method = local.api_method authorization = var.authorization } diff --git a/infrastructure/stacks/triage/trigger.tf b/infrastructure/stacks/triage/trigger.tf index fce000ab..3311e8b4 100644 --- a/infrastructure/stacks/triage/trigger.tf +++ b/infrastructure/stacks/triage/trigger.tf @@ -25,6 +25,6 @@ resource "aws_lambda_permission" "allowapig" { action = "lambda:InvokeFunction" function_name = module.apiglambda.lambda_function_name principal = "apigateway.amazonaws.com" - source_arn = "${aws_api_gateway_rest_api.triage.execution_arn}/*/*" + source_arn = "${aws_api_gateway_rest_api.triage.execution_arn}/*/${local.api_method}/${join("/", local.path_parts)}" qualifier = "live" } From 8c1bcdf289f6f5312f33d6b712b624923fb525b8 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 13 Feb 2026 12:24:28 +0000 Subject: [PATCH 129/181] testing api changes --- infrastructure/stacks/triage/restapi.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 5dd005d2..3c1a0375 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -46,7 +46,7 @@ resource "aws_api_gateway_integration" "triage" { http_method = aws_api_gateway_method.triage.http_method integration_http_method = var.int_http_method type = var.type - uri = module.apiglambda.lambda_function_invoke_arn + uri = aws_lambda_alias.apiglambda_live.invoke_arn } # Create integration responses for the API methods From 86bcee4317ef6d4790624d1c6aa7c93903808a98 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 13 Feb 2026 14:56:05 +0000 Subject: [PATCH 130/181] testing api changes --- infrastructure/stacks/triage/lambda.tf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index a7e740e2..6eab1666 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -19,9 +19,9 @@ module "s3lambda" { "ENVIRONMENT" = var.environment "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.s3_bucket_id - "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_arn - "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_arn - "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_arn + "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_name + "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_name + "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_name } account_id = data.aws_caller_identity.current.account_id } @@ -46,9 +46,9 @@ module "apiglambda" { "ENVIRONMENT" = var.environment "WORKSPACE" = terraform.workspace == "default" ? "" : terraform.workspace "DATA_RELEASE_BUCKET" = module.pathway_artifact_bucket.s3_bucket_id - "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_arn - "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_arn - "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_arn + "BODY_MAP_NODE_TABLE" = module.bodymaps.dynamodb_table_name + "STARTING_NODE_TABLE" = module.starting_coords.dynamodb_table_name + "TRIAGE_NODE_TABLE" = module.triage_nodes.dynamodb_table_name } account_id = data.aws_caller_identity.current.account_id } From cb3cb871a7c97247a6ff5dbac75bf0a2880ba80e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 11:12:33 +0000 Subject: [PATCH 131/181] testing api changes --- infrastructure/stacks/triage/security_group.tf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infrastructure/stacks/triage/security_group.tf b/infrastructure/stacks/triage/security_group.tf index 646fe4ca..09e02775 100644 --- a/infrastructure/stacks/triage/security_group.tf +++ b/infrastructure/stacks/triage/security_group.tf @@ -5,3 +5,12 @@ resource "aws_security_group" "processor_lambda_security_group" { vpc_id = data.aws_vpc.vpc.id } + +resource "aws_vpc_security_group_egress_rule" "allow_dynamodb_access_from_organisation_api" { + security_group_id = aws_security_group.organisation_api_lambda_security_group.id + description = "Organisation api egress rule to allow DynamoDB traffic" + prefix_list_id = data.aws_prefix_list.dynamodb.id + ip_protocol = "tcp" + from_port = var.https_port + to_port = var.https_port +} From 96337a7b3dfab2a3948ecdbdce0f99ee40f29598 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 11:23:32 +0000 Subject: [PATCH 132/181] testing api changes --- infrastructure/stacks/triage/data.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/data.tf b/infrastructure/stacks/triage/data.tf index b4b568cd..55772fc7 100644 --- a/infrastructure/stacks/triage/data.tf +++ b/infrastructure/stacks/triage/data.tf @@ -26,4 +26,6 @@ data "aws_subnet" "private_subnets_details" { for_each = toset(data.aws_subnets.private_subnets.ids) id = each.value } - +data "aws_prefix_list" "dynamodb" { + name = "com.amazonaws.${var.aws_region}.dynamodb" +} From 4abc20f50a2d42cc9ca91e34847fc9a42c6a5134 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 11:26:55 +0000 Subject: [PATCH 133/181] testing api changes --- infrastructure/stacks/triage/security_group.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/security_group.tf b/infrastructure/stacks/triage/security_group.tf index 09e02775..b5f07e0c 100644 --- a/infrastructure/stacks/triage/security_group.tf +++ b/infrastructure/stacks/triage/security_group.tf @@ -7,7 +7,7 @@ resource "aws_security_group" "processor_lambda_security_group" { } resource "aws_vpc_security_group_egress_rule" "allow_dynamodb_access_from_organisation_api" { - security_group_id = aws_security_group.organisation_api_lambda_security_group.id + security_group_id = aws_security_group.processor_lambda_security_group.id description = "Organisation api egress rule to allow DynamoDB traffic" prefix_list_id = data.aws_prefix_list.dynamodb.id ip_protocol = "tcp" From 7c65e2ba7940f4ceb398842b6e02fc17495209a9 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 12:31:43 +0000 Subject: [PATCH 134/181] testing api changes --- infrastructure/stacks/triage/data.tf | 4 ++++ infrastructure/stacks/triage/security_group.tf | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/infrastructure/stacks/triage/data.tf b/infrastructure/stacks/triage/data.tf index 55772fc7..d20342c1 100644 --- a/infrastructure/stacks/triage/data.tf +++ b/infrastructure/stacks/triage/data.tf @@ -29,3 +29,7 @@ data "aws_subnet" "private_subnets_details" { data "aws_prefix_list" "dynamodb" { name = "com.amazonaws.${var.aws_region}.dynamodb" } + +data "aws_prefix_list" "s3" { + name = "com.amazonaws.${var.aws_region}.s3" +} diff --git a/infrastructure/stacks/triage/security_group.tf b/infrastructure/stacks/triage/security_group.tf index b5f07e0c..cdba3b33 100644 --- a/infrastructure/stacks/triage/security_group.tf +++ b/infrastructure/stacks/triage/security_group.tf @@ -14,3 +14,12 @@ resource "aws_vpc_security_group_egress_rule" "allow_dynamodb_access_from_organi from_port = var.https_port to_port = var.https_port } + +resource "aws_vpc_security_group_egress_rule" "allow_s3_access_from_processor_lambda" { + security_group_id = aws_security_group.processor_lambda_security_group.id + description = "Processor lambda egress rule to allow S3 traffic" + prefix_list_id = data.aws_prefix_list.s3.id + ip_protocol = "tcp" + from_port = var.https_port + to_port = var.https_port +} From be560e52d93967b0861c0ffde1c8aabdd38b5458 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 12:39:39 +0000 Subject: [PATCH 135/181] testing api changes --- infrastructure/stacks/triage/lambda.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 6eab1666..1737fa31 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -9,6 +9,7 @@ module "s3lambda" { runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size + timeout = 60 description = "Lambda function for S3 event processing in SAET Triage API" subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] From 8b319c4964dd3e8cf3e79cd318bab622f113f524 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 12:47:58 +0000 Subject: [PATCH 136/181] testing api changes --- infrastructure/stacks/triage/iam.tf | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index c29320aa..d0c6f2fc 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -43,7 +43,31 @@ resource "aws_iam_policy" "s3_access" { # all objects inside "arn:aws:s3:::${module.pathway_artifact_bucket.s3_bucket_id}/*" ] - }] + }, + { + Effect = "Allow", + Action = [ + "dynamodb:GetItem", + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:Query", + "dynamodb:Scan" + ], + Resource = concat( + [ + module.starting_coords.dynamodb_table_arn, + module.triage_nodes.dynamodb_table_arn, + module.bodymaps.dynamodb_table_arn + ], + [ + "${module.starting_coords.dynamodb_table_arn}/index/*", + "${module.triage_nodes.dynamodb_table_arn}/index/*", + "${module.bodymaps.dynamodb_table_arn}/index/*" + ] + ) + } + ] }) } From e4d600dba58dd1da6684ea2509f0a8c06a27f978 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Mon, 16 Feb 2026 15:41:55 +0000 Subject: [PATCH 137/181] testing api changes --- infrastructure/stacks/triage/iam.tf | 23 ------------------- .../stacks/triage/security_group.tf | 6 ++--- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index d0c6f2fc..f2701056 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -44,29 +44,6 @@ resource "aws_iam_policy" "s3_access" { "arn:aws:s3:::${module.pathway_artifact_bucket.s3_bucket_id}/*" ] }, - { - Effect = "Allow", - Action = [ - "dynamodb:GetItem", - "dynamodb:BatchGetItem", - "dynamodb:BatchWriteItem", - "dynamodb:PutItem", - "dynamodb:Query", - "dynamodb:Scan" - ], - Resource = concat( - [ - module.starting_coords.dynamodb_table_arn, - module.triage_nodes.dynamodb_table_arn, - module.bodymaps.dynamodb_table_arn - ], - [ - "${module.starting_coords.dynamodb_table_arn}/index/*", - "${module.triage_nodes.dynamodb_table_arn}/index/*", - "${module.bodymaps.dynamodb_table_arn}/index/*" - ] - ) - } ] }) } diff --git a/infrastructure/stacks/triage/security_group.tf b/infrastructure/stacks/triage/security_group.tf index cdba3b33..ad3faa11 100644 --- a/infrastructure/stacks/triage/security_group.tf +++ b/infrastructure/stacks/triage/security_group.tf @@ -1,4 +1,4 @@ -resource "aws_security_group" "processor_lambda_security_group" { +resource "aws_security_group" "triage_api_lambda_security_group" { # checkov:skip=CKV2_AWS_5: False positive due to module reference name = "${local.resource_prefix}-${var.processor_lambda_name}${local.workspace_suffix}-sg" description = "Security group for processor lambda" @@ -7,7 +7,7 @@ resource "aws_security_group" "processor_lambda_security_group" { } resource "aws_vpc_security_group_egress_rule" "allow_dynamodb_access_from_organisation_api" { - security_group_id = aws_security_group.processor_lambda_security_group.id + security_group_id = aws_security_group.triage_api_lambda_security_group.id description = "Organisation api egress rule to allow DynamoDB traffic" prefix_list_id = data.aws_prefix_list.dynamodb.id ip_protocol = "tcp" @@ -16,7 +16,7 @@ resource "aws_vpc_security_group_egress_rule" "allow_dynamodb_access_from_organi } resource "aws_vpc_security_group_egress_rule" "allow_s3_access_from_processor_lambda" { - security_group_id = aws_security_group.processor_lambda_security_group.id + security_group_id = aws_security_group.triage_api_lambda_security_group.id description = "Processor lambda egress rule to allow S3 traffic" prefix_list_id = data.aws_prefix_list.s3.id ip_protocol = "tcp" From f2698343a507b85637d715fdb5ba81414b19d893 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 09:55:23 +0000 Subject: [PATCH 138/181] adding Derrive workspace --- .github/actions/derive-workspace/action.yaml | 24 +++++++++++++ .github/workflows/metadata.yaml | 34 +++++++++---------- .../pipeline-deploy-application.yaml | 6 ++-- .github/workflows/test-workflow.yaml | 31 ----------------- 4 files changed, 44 insertions(+), 51 deletions(-) create mode 100644 .github/actions/derive-workspace/action.yaml delete mode 100644 .github/workflows/test-workflow.yaml diff --git a/.github/actions/derive-workspace/action.yaml b/.github/actions/derive-workspace/action.yaml new file mode 100644 index 00000000..518bce45 --- /dev/null +++ b/.github/actions/derive-workspace/action.yaml @@ -0,0 +1,24 @@ +name: "Derive Workspace action" +description: "Derives the name of the workspace for subsequent actions to run against" + +outputs: + workspace: + description: "The derived workspace name" + value: ${{ steps.derive-workspace.outputs.workspace }} + +runs: + using: "composite" + steps: + - name: "Derive workspace" + id: "derive-workspace" + shell: bash + run: | + export TRIGGER=${{ github.ref_type }} + export TRIGGER_ACTION=${{ github.event_name }} + export TRIGGER_REFERENCE=${{ github.ref_name }} + export TRIGGER_HEAD_REFERENCE=${{ github.head_ref }} + export TRIGGER_EVENT_REF=${{ github.event.ref}} + export COMMIT_HASH=$(git rev-parse --short $GITHUB_SHA) + . scripts/workflow/derive-workspace.sh + echo "Workspace Name: ${WORKSPACE}" + echo "workspace=${WORKSPACE}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/metadata.yaml b/.github/workflows/metadata.yaml index dd1b6ce9..9a31de8f 100644 --- a/.github/workflows/metadata.yaml +++ b/.github/workflows/metadata.yaml @@ -22,9 +22,9 @@ on: reponame: description: "The name of the code repo" value: ${{ jobs.echo-metadata.outputs.reponame }} - # workspace: - # description: "The name of the workspace that we interacting with" - # value: ${{ jobs.derive-workspace.outputs.workspace }} + workspace: + description: "The name of the workspace that we interacting with" + value: ${{ jobs.derive-workspace.outputs.workspace }} artefact_bucket_name: description: "The s3 bucket for domain artefacts" value: ${{ jobs.echo-metadata.outputs.artefact_bucket_name }} @@ -96,21 +96,21 @@ jobs: id: get_metadata uses: ./.github/actions/metadata - # derive-workspace: - # name: "Derive workspace" - # runs-on: ubuntu-latest - # timeout-minutes: ${{ inputs.workflow_timeout }} - # outputs: - # workspace: ${{ steps.derive_workspace.outputs.workspace }} - # steps: - # - name: "Checkout code" - # uses: actions/checkout@v6 - # with: - # ref: ${{ inputs.tag }} + derive-workspace: + name: "Derive workspace" + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.workflow_timeout }} + outputs: + workspace: ${{ steps.derive_workspace.outputs.workspace }} + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} - # - name: "Derive workspace" - # id: derive_workspace - # uses: ./.github/actions/derive-workspace + - name: "Derive workspace" + id: derive_workspace + uses: ./.github/actions/derive-workspace set-environment: name: "Set environment" diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 728c69da..a4a748ad 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -76,7 +76,7 @@ jobs: name: "Perform static code analysis" needs: - metadata - # - build-services + - build-services uses: ./.github/workflows/static-code-analysis.yaml with: environment: ${{ needs.metadata.outputs.environment }} @@ -91,7 +91,7 @@ jobs: uses: ./.github/workflows/deploy-application-infrastructure.yaml with: environment: ${{ needs.metadata.outputs.environment }} - workspace: "default" + workspace: ${{ needs.metadata.outputs.workspace }} application_tag: ${{ inputs.application_tag || 'latest' }} tag: ${{ inputs.tag }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} @@ -101,6 +101,6 @@ jobs: check-pipeline-status: name: "Check Pipeline Status" needs: - - deploy-application-infrastructure + - deploy-data-migration-service if: always() uses: ./.github/workflows/pipeline-status-check.yaml diff --git a/.github/workflows/test-workflow.yaml b/.github/workflows/test-workflow.yaml deleted file mode 100644 index bf64bbb0..00000000 --- a/.github/workflows/test-workflow.yaml +++ /dev/null @@ -1,31 +0,0 @@ -name: test-workflow - -on: - workflow_dispatch: - -env: - AWS_REGION: "eu-west-2" - -permissions: - contents: read - id-token: write - -jobs: - saet-test-checks: - name: "test-workflow" - runs-on: ubuntu-latest - - steps: - - name: Git clone the repository - uses: actions/checkout@v6.0.1 - - - name: configure aws credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/saet-triage-api-dev-account-github-runner - role-session-name: GitHub_to_AWS_via_FederatedOIDC - aws-region: ${{ env.AWS_REGION }} - - - name: Create S3 bucket - run: | - aws s3api create-bucket --bucket saet-test-bucket-12345 --create-bucket-configuration LocationConstraint=eu-west-2 From a7d7e3df2b90db198d462b033931c037a237f294 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:06:13 +0000 Subject: [PATCH 139/181] adding Derrive workspace --- .github/workflows/pipeline-deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index a4a748ad..c262258a 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -76,7 +76,7 @@ jobs: name: "Perform static code analysis" needs: - metadata - - build-services + # - build-services uses: ./.github/workflows/static-code-analysis.yaml with: environment: ${{ needs.metadata.outputs.environment }} From eb98066ade91c514771063bd7b057c829d2c53bf Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:09:07 +0000 Subject: [PATCH 140/181] adding Derrive workspace --- .github/workflows/pipeline-deploy-application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index c262258a..5fadae38 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -101,6 +101,6 @@ jobs: check-pipeline-status: name: "Check Pipeline Status" needs: - - deploy-data-migration-service + # - deploy-data-migration-service if: always() uses: ./.github/workflows/pipeline-status-check.yaml From 51bdb2dfda059bc806253f6b4f653b066f3aae12 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:10:49 +0000 Subject: [PATCH 141/181] adding Derrive workspace --- .github/workflows/pipeline-deploy-application.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 5fadae38..87cab239 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -100,7 +100,7 @@ jobs: check-pipeline-status: name: "Check Pipeline Status" - needs: - # - deploy-data-migration-service + # needs: + # - deploy-data-migration-service if: always() uses: ./.github/workflows/pipeline-status-check.yaml From 613881e0e06b9a49a90c057a45dbfb81e41dd9c7 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:18:13 +0000 Subject: [PATCH 142/181] adding Derrive workspace --- scripts/workflow/derive-workspace.sh | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 scripts/workflow/derive-workspace.sh diff --git a/scripts/workflow/derive-workspace.sh b/scripts/workflow/derive-workspace.sh new file mode 100755 index 00000000..7ea1ca4a --- /dev/null +++ b/scripts/workflow/derive-workspace.sh @@ -0,0 +1,60 @@ +#! /bin/bash +echo "Trigger: $TRIGGER" +echo "Trigger action: $TRIGGER_ACTION" +echo "Trigger reference: $TRIGGER_REFERENCE" +echo "Trigger head reference: $TRIGGER_HEAD_REFERENCE " +echo "Trigger event reference $TRIGGER_EVENT_REF" +echo "Commit hash (for dependabot only): $COMMIT_HASH" + +WORKSPACE="Unknown" + +# If we are dealing with a tagging action, then the workspace is the name of the tag +if [ "$TRIGGER" == "tag" ] ; then + WORKSPACE="$TRIGGER_REFERENCE" + echo "Triggered by tagging - deriving workspace directly from tag: $TRIGGER_REFERENCE" + echo "Workspace: $WORKSPACE" + export WORKSPACE + exit +fi + +# If we are dealing with a push action or workflow_dispatch and the trigger is not a tag, we'll need to look at the branch name +# to derive the workspace +if [ "$TRIGGER_ACTION" == "push" ] || [ "$TRIGGER_ACTION" == "workflow_dispatch" ] ; then + echo "Triggered by a push or workflow_dispatch - branch name is current branch" + BRANCH_NAME="${BRANCH_NAME:-$(git rev-parse --abbrev-ref HEAD)}" +# If trigger action is pull_request we will need to derive the workspace from the triggering head reference +elif [ "$TRIGGER_ACTION" == "pull_request" ] ; then + echo "Triggered by a pull_request - setting branch name to trigger head ref " + BRANCH_NAME="$TRIGGER_HEAD_REFERENCE" +# If trigger action is delete (of branch) we will need to derive the workspace from the event ref +elif [ "$TRIGGER_ACTION" == "delete" ] ; then + echo "Triggered by a branch deletion - setting branch name to trigger event ref " + BRANCH_NAME="$TRIGGER_EVENT_REF" +fi + +echo "Branch name: $BRANCH_NAME" +BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/refs\/heads\/task/task/g; s/refs\/heads\/dependabot/dependabot/g') + +if [[ "${BRANCH_NAME:0:10}" == "dependabot" ]]; then + # Handle dependabot branches + WORKSPACE="bot-$COMMIT_HASH" + echo "Workspace from dependabot branch: $WORKSPACE" +elif [[ "$BRANCH_NAME" == "main" ]]; then + # Handle main branch + WORKSPACE="default" + echo "Workspace from main branch: $WORKSPACE" +elif [[ "$BRANCH_NAME" == "develop" ]]; then + # Handle develop branch + WORKSPACE="default" + echo "Workspace from develop branch: $WORKSPACE" +else + # Handle task branches + IFS='/' read -r -a name_array <<< "$BRANCH_NAME" + IFS='_-' read -r -a ref <<< "${name_array[1]}" + WORKSPACE=$(echo "${ref[0]}-${ref[1]}" | tr "[:upper:]" "[:lower:]") + echo "Workspace from feature branch: $WORKSPACE" +fi + +echo "Branch name: $BRANCH_NAME" +echo "Workspace: $WORKSPACE" +export WORKSPACE From bd5d5724453effa6579aa21fb72962d9a8ef97dd Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:23:28 +0000 Subject: [PATCH 143/181] adding Derrive workspace --- infrastructure/stacks/triage/lambda.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 1737fa31..0e65083e 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -13,7 +13,7 @@ module "s3lambda" { description = "Lambda function for S3 event processing in SAET Triage API" subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] - security_group_ids = [aws_security_group.processor_lambda_security_group.id] + security_group_ids = [aws_security_group.allow_s3_access_from_processor_lambda.id] vpc_id = data.aws_vpc.vpc.id environment_variables = { @@ -40,7 +40,7 @@ module "apiglambda" { description = "Lambda function for App gateway processing event in SAET Triage API" subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] - security_group_ids = [aws_security_group.processor_lambda_security_group.id] + security_group_ids = [aws_security_group.allow_dynamodb_access_from_organisation_api.id] vpc_id = data.aws_vpc.vpc.id environment_variables = { From 1c94ac9d69e34df8bb17c4cf12a36212c19c40cc Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:26:12 +0000 Subject: [PATCH 144/181] adding Derrive workspace --- infrastructure/stacks/triage/lambda.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 0e65083e..ccd06e43 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -13,7 +13,7 @@ module "s3lambda" { description = "Lambda function for S3 event processing in SAET Triage API" subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] - security_group_ids = [aws_security_group.allow_s3_access_from_processor_lambda.id] + security_group_ids = [aws_security_group.triage_api_lambda_security_group.id] vpc_id = data.aws_vpc.vpc.id environment_variables = { @@ -40,7 +40,7 @@ module "apiglambda" { description = "Lambda function for App gateway processing event in SAET Triage API" subnet_ids = [for subnet in data.aws_subnet.private_subnets_details : subnet.id] - security_group_ids = [aws_security_group.allow_dynamodb_access_from_organisation_api.id] + security_group_ids = [aws_security_group.triage_api_lambda_security_group.id] vpc_id = data.aws_vpc.vpc.id environment_variables = { From 1d0d7e48a11b8ffda0596098cf8cd021fcde25a8 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:37:15 +0000 Subject: [PATCH 145/181] adding Derrive workspace --- infrastructure/modules/lambda/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/lambda/main.tf b/infrastructure/modules/lambda/main.tf index e49b7e39..bcc2f640 100644 --- a/infrastructure/modules/lambda/main.tf +++ b/infrastructure/modules/lambda/main.tf @@ -16,15 +16,15 @@ module "lambda" { timeout = var.timeout memory_size = var.memory_size - create_package = var.s3_bucket_name == "" ? var.create_package : false - local_existing_package = var.s3_bucket_name == "" ? var.local_existing_package : null + create_package = var.create_package + local_existing_package = var.create_package ? var.local_existing_package : null ignore_source_code_hash = var.ignore_source_code_hash allowed_triggers = var.allowed_triggers - s3_existing_package = var.s3_bucket_name != "" ? { + s3_existing_package = var.create_package ? null : { bucket = var.s3_bucket_name key = var.s3_key - } : null + } vpc_subnet_ids = var.subnet_ids vpc_security_group_ids = var.security_group_ids From 7aa2ce78b20740b52f4c686d4f080fecae216863 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 10:50:50 +0000 Subject: [PATCH 146/181] adding Derrive workspace --- infrastructure/modules/dynamodb/locals.tf | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 infrastructure/modules/dynamodb/locals.tf diff --git a/infrastructure/modules/dynamodb/locals.tf b/infrastructure/modules/dynamodb/locals.tf new file mode 100644 index 00000000..350cb687 --- /dev/null +++ b/infrastructure/modules/dynamodb/locals.tf @@ -0,0 +1,6 @@ +# ============================================================================== +# Context + +locals { + workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" +} From 1e1d1537aac4c3576a615ccb2e6309f0541f2e7a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 14:14:42 +0000 Subject: [PATCH 147/181] adding Derrive workspace --- infrastructure/modules/dynamodb/main.tf | 2 +- infrastructure/stacks/triage/iam.tf | 6 +++--- infrastructure/stacks/triage/restapi.tf | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/modules/dynamodb/main.tf b/infrastructure/modules/dynamodb/main.tf index 46fc1295..8aada51c 100755 --- a/infrastructure/modules/dynamodb/main.tf +++ b/infrastructure/modules/dynamodb/main.tf @@ -2,7 +2,7 @@ module "dynamodb_table" { # Module version: 5.5.0 source = "git::https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git?ref=45c9cb10c2f6209e7362bba92cadd5ab3c9e2003" - name = var.table_name + name = "${var.table_name}${local.workspace_suffix}" hash_key = var.hash_key range_key = var.range_key autoscaling_enabled = var.autoscaling_enabled diff --git a/infrastructure/stacks/triage/iam.tf b/infrastructure/stacks/triage/iam.tf index f2701056..5a09ac8b 100644 --- a/infrastructure/stacks/triage/iam.tf +++ b/infrastructure/stacks/triage/iam.tf @@ -22,11 +22,11 @@ resource "aws_iam_role" "lambda_role" { Action = "sts:AssumeRole" }] }) - name = "${local.resource_prefix}-lambda-role" + name = "${local.resource_prefix}-lambda-role${local.workspace_suffix}" } resource "aws_iam_policy" "s3_access" { - name = "${local.resource_prefix}-s3-access" + name = "${local.resource_prefix}-s3-access${local.workspace_suffix}" policy = jsonencode({ Version = "2012-10-17", Statement = [ @@ -49,7 +49,7 @@ resource "aws_iam_policy" "s3_access" { } resource "aws_iam_policy" "ddb_access" { - name = "${local.resource_prefix}-dynamo-db-access" + name = "${local.resource_prefix}-dynamo-db-access${local.workspace_suffix}" policy = jsonencode({ Version = "2012-10-17", Statement = [ diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index 3c1a0375..aa3d96a4 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -4,7 +4,7 @@ locals { } resource "aws_api_gateway_rest_api" "triage" { - name = var.api_name + name = "${var.api_name}${local.workspace_suffix}" } resource "aws_api_gateway_resource" "fhir" { From 035a7397d14fc0ce7a1ecd2bcaf79ef10092160b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 14:42:03 +0000 Subject: [PATCH 148/181] adding Derrive workspace --- infrastructure/modules/dynamodb/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/modules/dynamodb/variables.tf b/infrastructure/modules/dynamodb/variables.tf index ee4462bb..35ac016e 100755 --- a/infrastructure/modules/dynamodb/variables.tf +++ b/infrastructure/modules/dynamodb/variables.tf @@ -74,7 +74,7 @@ variable "stream_enabled" { variable "stream_view_type" { description = "Determines the information written to the stream when an item is modified." type = string - default = "NEW_AND_OLD_IMAGES" + default = null } variable "attributes" { From 51e41449318bfe642919b68255d9aeb227ec108e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 15:04:58 +0000 Subject: [PATCH 149/181] adding Derrive workspace --- infrastructure/stacks/triage/restapi.tf | 4 +++- infrastructure/stacks/triage/s3.tf | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/triage/restapi.tf b/infrastructure/stacks/triage/restapi.tf index aa3d96a4..cd878eb0 100644 --- a/infrastructure/stacks/triage/restapi.tf +++ b/infrastructure/stacks/triage/restapi.tf @@ -56,6 +56,8 @@ resource "aws_api_gateway_integration_response" "triage" { http_method = aws_api_gateway_method.triage.http_method status_code = aws_api_gateway_method_response.triage.status_code selection_pattern = "" + + depends_on = [aws_api_gateway_integration.triage] } # Create a deployment to publish the API @@ -71,7 +73,7 @@ resource "aws_api_gateway_deployment" "deployment" { # Force redeployment when any API resources change triggers = { redeployment = sha1(jsonencode({ - resources = { + resources = { fhir = aws_api_gateway_resource.fhir r4 = aws_api_gateway_resource.r4 triage = aws_api_gateway_resource.triage diff --git a/infrastructure/stacks/triage/s3.tf b/infrastructure/stacks/triage/s3.tf index 24968d57..4f8ff06f 100644 --- a/infrastructure/stacks/triage/s3.tf +++ b/infrastructure/stacks/triage/s3.tf @@ -1,4 +1,4 @@ module "pathway_artifact_bucket" { source = "../../modules/s3" - bucket_name = "${local.resource_prefix}-artifact" + bucket_name = "${local.resource_prefix}-artifact${local.workspace_suffix}" } From ebcd172bc0d6137fcd6afaf2cf43fee3084aee96 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 15:17:06 +0000 Subject: [PATCH 150/181] adding Derrive workspace --- infrastructure/stacks/triage/lambda.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index ccd06e43..d29f54ae 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -3,7 +3,7 @@ module "s3lambda" { source = "../../modules/lambda" aws_region = var.aws_region function_name = "${local.resource_prefix}-s3-Lambda" - policy_jsons = [aws_iam_policy.s3_access.policy] + policy_jsons = [aws_iam_policy.s3_access.policy, aws_iam_policy.ddb_access.policy] handler = "s3_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id runtime = var.runtime @@ -31,7 +31,7 @@ module "apiglambda" { source = "../../modules/lambda" aws_region = var.aws_region function_name = "${local.resource_prefix}-apig-Lambda" - policy_jsons = [aws_iam_policy.ddb_access.policy] + policy_jsons = [aws_iam_policy.ddb_access.policy, aws_iam_policy.s3_access.policy] handler = "api_gateway_configurator.handler" s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id runtime = var.runtime From 9e79229757d2c5e2357cb4e04fcccff9293fae88 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Tue, 17 Feb 2026 15:38:21 +0000 Subject: [PATCH 151/181] adding Derrive workspace --- .../actions/artefact-cleardown/action.yaml | 20 +++ .github/actions/check-tf-state/action.yaml | 20 +++ .github/workflows/artefacts-cleardown.yaml | 56 ++++++++ .../workflows/infrastructure-cleardown.yaml | 124 ++++++++++++++++++ .../pipeline-infrastructure-cleardown.yaml | 69 ++++++++++ scripts/workflow/check-terraform-state.sh | 35 +++++ scripts/workflow/cleardown-artefacts.sh | 38 ++++++ 7 files changed, 362 insertions(+) create mode 100644 .github/actions/artefact-cleardown/action.yaml create mode 100644 .github/actions/check-tf-state/action.yaml create mode 100644 .github/workflows/artefacts-cleardown.yaml create mode 100644 .github/workflows/infrastructure-cleardown.yaml create mode 100644 .github/workflows/pipeline-infrastructure-cleardown.yaml create mode 100755 scripts/workflow/check-terraform-state.sh create mode 100755 scripts/workflow/cleardown-artefacts.sh diff --git a/.github/actions/artefact-cleardown/action.yaml b/.github/actions/artefact-cleardown/action.yaml new file mode 100644 index 00000000..85f8b776 --- /dev/null +++ b/.github/actions/artefact-cleardown/action.yaml @@ -0,0 +1,20 @@ +name: "Cleardown redundant artefacts action" +description: "Delete the redundant artefacts" +inputs: + workspace: + description: "The name of the workspace to action the infrastructure into." + required: true + artefact_bucket_name: + description: "The name of the s3 bucket holding domain artefacts" + required: true + +runs: + using: composite + steps: + - name: Delete artefacts + id: delete_artefacts + shell: bash + run: | + export WORKSPACE=${{inputs.workspace}} + export ARTEFACT_BUCKET_NAME=${{inputs.artefact_bucket_name}} + ./scripts/workflow/cleardown-artefacts.sh diff --git a/.github/actions/check-tf-state/action.yaml b/.github/actions/check-tf-state/action.yaml new file mode 100644 index 00000000..bbc8cfb1 --- /dev/null +++ b/.github/actions/check-tf-state/action.yaml @@ -0,0 +1,20 @@ +name: "Check terraform state cleardown action" +description: "Check deletion of terraform state" +inputs: + workspace: + description: "The name of the workspace to check." + required: true + environment: + description: "The name of the environment to action the infrastructure into." + required: true + +runs: + using: composite + steps: + - name: Delete terraform state + id: delete_tf_state + shell: bash + run: | + export WORKSPACE=${{inputs.workspace}} + export ENVIRONMENT=${{inputs.environment}} + ./scripts/workflow/check-terraform-state.sh diff --git a/.github/workflows/artefacts-cleardown.yaml b/.github/workflows/artefacts-cleardown.yaml new file mode 100644 index 00000000..2204b116 --- /dev/null +++ b/.github/workflows/artefacts-cleardown.yaml @@ -0,0 +1,56 @@ +name: Cleardown Artefacts + +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + environment: + description: "Defines the Github environment in which to pull environment variables from" + required: true + type: string + workspace: + description: "Name of the workspace" + required: true + type: string + workflow_timeout: + description: "Timeout duration in minutes" + required: false + default: 10 + type: number + artefact_bucket_name: + description: "The name of the s3 bucket holding domain artefacts" + required: true + type: string + type: + description: "The type of permissions (e.g., account, app)" + required: true + type: string + +jobs: + cleardown-artefacts: + name: "Cleardown redundant artefacts" + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.workflow_timeout }} + environment: ${{ inputs.environment }} + + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Cleardown redundant artefacts" + uses: ./.github/actions/artefact-cleardown + with: + workspace: ${{ inputs.workspace }} + artefact_bucket_name: ${{ inputs.artefact_bucket_name }} diff --git a/.github/workflows/infrastructure-cleardown.yaml b/.github/workflows/infrastructure-cleardown.yaml new file mode 100644 index 00000000..5cd6d237 --- /dev/null +++ b/.github/workflows/infrastructure-cleardown.yaml @@ -0,0 +1,124 @@ +name: Cleardown Infrastructure + +permissions: + id-token: write + contents: read +on: + workflow_call: + inputs: + environment: + description: "Defines the Github environment in which to pull environment variables from" + required: true + type: string + workspace: + description: "Name of the workspace" + required: true + type: string + project: + description: "The project - eg dos or cm." + required: false + default: "dos" + type: string + stacks: + description: "Name of the stacks" + required: true + type: string + tag: + description: "Name of the tag" + required: false + type: string + workflow_timeout: + description: "Timeout duration in minutes" + required: false + default: 10 + type: number + application_tag: + description: "The application tag identifying the timeline in the repository to deploy from" + required: false + type: string + commit_hash: + description: "The commit hash, set by the CI/CD pipeline workflow" + required: false + type: string + type: + description: "The type of permissions (e.g., account, app)" + required: true + type: string + +jobs: + destroy-application-infrastructure: + uses: ./.github/workflows/deploy-infrastructure.yaml + with: + environment: ${{ inputs.environment }} + workspace: ${{ inputs.workspace }} + project: ${{ inputs.project }} + stacks: ${{ inputs.stacks }} + tag: ${{ inputs.tag }} + workflow_timeout: ${{ inputs.workflow_timeout }} + application_tag: ${{ inputs.application_tag }} + commit_hash: ${{ inputs.commit_hash }} + action: destroy + type: ${{ inputs.type }} + secrets: inherit + + delete-tf-state: + name: "Delete terraform state file" + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.workflow_timeout }} + environment: ${{ inputs.environment }} + strategy: + matrix: + stack: ${{ fromJSON(inputs.stacks) }} + needs: + - destroy-application-infrastructure + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Delete terraform state file" + id: delete_tf_state + uses: ./.github/actions/cleardown-tf-state + with: + workspace: ${{ inputs.workspace }} + environment: ${{ inputs.environment }} + stack: ${{ matrix.stack }} + + check-tf-state: + name: "Check state files cleared down" + runs-on: ubuntu-latest + timeout-minutes: ${{ inputs.workflow_timeout }} + environment: ${{ inputs.environment }} + + needs: + - delete-tf-state + steps: + - name: "Checkout code" + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: "Configure AWS Credentials" + uses: ./.github/actions/configure-credentials + with: + aws_account_id: ${{ secrets.ACCOUNT_ID }} + aws_region: ${{ vars.AWS_REGION }} + type: ${{ inputs.type }} + environment: ${{ inputs.environment }} + + - name: "Check terraform state file" + id: check_tf_state + uses: ./.github/actions/check-tf-state + with: + workspace: ${{ inputs.workspace }} + environment: ${{ inputs.environment }} + stack: ${{ matrix.stack }} diff --git a/.github/workflows/pipeline-infrastructure-cleardown.yaml b/.github/workflows/pipeline-infrastructure-cleardown.yaml new file mode 100644 index 00000000..9ff7f227 --- /dev/null +++ b/.github/workflows/pipeline-infrastructure-cleardown.yaml @@ -0,0 +1,69 @@ +name: Cleardown Application Infrastructure Pipeline +# Intended to run if +# actor is not the queue bot and +# the branch deleted is either +# a task branch or +# a dependabot branch +permissions: + id-token: write + contents: read + +on: + delete: + repository_dispatch: + types: [release-build-cleardown] + workflow_dispatch: + # checkov:skip=CKV_GHA_7:Inputs reviewed and approved + inputs: + application_tag: + description: "Specify the application tag to cleardown" + required: true + default: "latest" + type: string + environment: + description: "Specify the environment to cleardown" + required: true + default: "dev" + type: choice + options: + - dev + - test + workspace: + description: "Specify the workspace to cleardown" + required: true + type: string + +jobs: + metadata: + if: github.actor != 'github-merge-queue[bot]' + name: "Get Metadata" + uses: ./.github/workflows/metadata.yaml + + cleardown-infrastructure: + name: "Cleardown Infrastructure" + needs: + - metadata + uses: ./.github/workflows/infrastructure-cleardown.yaml + with: + environment: ${{ github.event.client_payload.environment || inputs.environment || needs.metadata.outputs.environment }} + workspace: ${{ github.event.client_payload.workspace || inputs.workspace || needs.metadata.outputs.workspace }} + stacks: "['triage]" + application_tag: ${{ inputs.application_tag || github.event.client_payload.application_tag || 'latest' }} + commit_hash: ${{ needs.metadata.outputs.commit_hash }} + workflow_timeout: 30 + type: app + secrets: inherit + + cleardown-artefacts: + if: github.actor != 'github-actions[bot]' + name: "Cleardown Artefacts" + needs: + - metadata + - cleardown-infrastructure + uses: ./.github/workflows/artefacts-cleardown.yaml + with: + environment: ${{ inputs.environment || needs.metadata.outputs.environment }} + workspace: ${{ inputs.workspace || needs.metadata.outputs.workspace }} + artefact_bucket_name: "${{ needs.metadata.outputs.reponame }}-${{ needs.metadata.outputs.mgmt_environment || inputs.environment }}-artefacts-bucket" + type: app + secrets: inherit diff --git a/scripts/workflow/check-terraform-state.sh b/scripts/workflow/check-terraform-state.sh new file mode 100755 index 00000000..25105174 --- /dev/null +++ b/scripts/workflow/check-terraform-state.sh @@ -0,0 +1,35 @@ +#! /bin/bash + +# fail on first error +set -e +EXPORTS_SET=0 + +# check necessary environment variables are set +if [ -z "$WORKSPACE" ] ; then + echo Set WORKSPACE + EXPORTS_SET=1 +fi + +if [ -z "$ENVIRONMENT" ] ; then + echo Set ENVIRONMENT + EXPORTS_SET=1 +fi + +if [ $EXPORTS_SET = 1 ] ; then + echo One or more exports not set + exit 1 +fi + +export TF_VAR_repo_name="${REPOSITORY:-"$(basename -s .git "$(git config --get remote.origin.url)")"}" +export TERRAFORM_BUCKET_NAME="nhse-$ENVIRONMENT-$TF_VAR_repo_name-terraform-state" + +echo "Checking for terraform workspace --> $WORKSPACE" +echo "Terraform state S3 bucket name being checked --> $TERRAFORM_BUCKET_NAME" + +CLEARED=$(aws s3 ls s3://$TERRAFORM_BUCKET_NAME/env:/$WORKSPACE/ | awk '{print $2}') +if [ -n "$CLEARED" ] ; then + echo "Not all state cleared - $CLEARED" + exit 1 +else + echo All state entry for $WORKSPACE removed +fi diff --git a/scripts/workflow/cleardown-artefacts.sh b/scripts/workflow/cleardown-artefacts.sh new file mode 100755 index 00000000..3b0c3279 --- /dev/null +++ b/scripts/workflow/cleardown-artefacts.sh @@ -0,0 +1,38 @@ +#! /bin/bash + +# fail on first error +set -e +EXPORTS_SET=0 + +# check necessary environment variables are set +if [ -z "$WORKSPACE" ] ; then + echo Set WORKSPACE + EXPORTS_SET=1 +fi + +if [ -z "$ARTEFACT_BUCKET_NAME" ] ; then + echo Set ARTEFACT_BUCKET_NAME + EXPORTS_SET=1 +fi + +if [ $EXPORTS_SET = 1 ] ; then + echo One or more exports not set + exit 1 +fi + +if [ "$WORKSPACE" == "default" ] ; then + echo WORKSPACE can not be default + exit 1 +fi + +echo "Clearing down artefacts at or below $ARTEFACT_BUCKET_NAME/$WORKSPACE" + +deletion_output=$(aws s3 rm --recursive s3://$ARTEFACT_BUCKET_NAME/$WORKSPACE/ 2>&1) + +if [ -n "$deletion_output" ]; then + echo "Sucessfully deleted following artefacts from $ARTEFACT_BUCKET_NAME/$WORKSPACE" + echo "$deletion_output" +else + echo "Problem deleting artefacts at $ARTEFACT_BUCKET_NAME/$WORKSPACE. Does targetted folder exist?" + echo "$deletion_output" +fi From 0b7114be7a502f0461610057ade8ef143fef2f4c Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Wed, 18 Feb 2026 15:52:41 +0000 Subject: [PATCH 152/181] Management account set up --- ...ipeline-deploy-account-infrastructure.yaml | 7 ++- .../workflows/pipeline-deploy-policies.yaml | 6 +++ .../environments/mgmt/account_security.tfvars | 1 + .../environments/mgmt/account_wide.tfvars | 20 +++++++ .../environments/mgmt/environment.tfvars | 3 ++ .../stacks/artefact_management/data.tf | 33 ++++++++++++ .../stacks/artefact_management/s3.tf | 52 +++++++++++++++++++ .../stacks/github_runner/oidc-provider.tf | 9 ++++ .../stacks/github_runner/variables.tf | 10 ++++ scripts/workflow/boostrapper.sh | 22 +++++--- 10 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 infrastructure/environments/mgmt/account_security.tfvars create mode 100644 infrastructure/environments/mgmt/account_wide.tfvars create mode 100644 infrastructure/environments/mgmt/environment.tfvars create mode 100644 infrastructure/stacks/artefact_management/data.tf create mode 100644 infrastructure/stacks/artefact_management/s3.tf create mode 100644 infrastructure/stacks/github_runner/oidc-provider.tf diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index ba71e45c..82daba25 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -89,6 +89,9 @@ jobs: - name: "env" environment: ${{ needs.metadata.outputs.environment }} stacks: "['terraform_management','account_wide']" + - name: "mgmt" + environment: ${{ needs.metadata.outputs.mgmt_environment }} + stacks: "['terraform_management','account_security','artefact_management']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} @@ -129,7 +132,9 @@ jobs: stacks: "['account_security']" - name: "env" environment: ${{ needs.metadata.outputs.environment }} - stacks: "['terraform_management','account_wide']" + - name: "mgmt" + environment: ${{ needs.metadata.outputs.mgmt_environment }} + stacks: "['terraform_management','account_wide','artefact_management']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index 5db4451b..53815189 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -73,6 +73,9 @@ jobs: - name: "account" environment: ${{ needs.metadata.outputs.account_type }} stacks: "['github_runner', 'account_policies']" + - name: "mgmt" + environment: ${{ needs.metadata.outputs.mgmt_environment }} + stacks: "['github_runner','account_policies']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} @@ -111,6 +114,9 @@ jobs: - name: "account" environment: ${{ needs.metadata.outputs.account_type }} stacks: "['github_runner', 'account_policies']" + - name: "mgmt" + environment: ${{ needs.metadata.outputs.mgmt_environment }} + stacks: "['github_runner','account_policies']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} diff --git a/infrastructure/environments/mgmt/account_security.tfvars b/infrastructure/environments/mgmt/account_security.tfvars new file mode 100644 index 00000000..cd2da3ef --- /dev/null +++ b/infrastructure/environments/mgmt/account_security.tfvars @@ -0,0 +1 @@ +enable_iam_analyzer = true diff --git a/infrastructure/environments/mgmt/account_wide.tfvars b/infrastructure/environments/mgmt/account_wide.tfvars new file mode 100644 index 00000000..d69d5cde --- /dev/null +++ b/infrastructure/environments/mgmt/account_wide.tfvars @@ -0,0 +1,20 @@ +vpc = { + name = "vpc" + cidr = "10.170.0.0/16" + + public_subnet_a = "10.170.0.0/21" + public_subnet_b = "10.170.8.0/21" + public_subnet_c = "10.170.16.0/21" + + private_subnet_a = "10.170.24.0/21" + private_subnet_b = "10.170.32.0/21" + private_subnet_c = "10.170.40.0/21" +} + +enable_flow_log = false +flow_log_s3_force_destroy = true + +# Single NAT Gateway +enable_nat_gateway = true +single_nat_gateway = true +one_nat_gateway_per_az = false diff --git a/infrastructure/environments/mgmt/environment.tfvars b/infrastructure/environments/mgmt/environment.tfvars new file mode 100644 index 00000000..50632e09 --- /dev/null +++ b/infrastructure/environments/mgmt/environment.tfvars @@ -0,0 +1,3 @@ +# environment specific values that are applicable to more than one stack +environment = "mgmt" +# data_classification = "3" diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf new file mode 100644 index 00000000..d60555f9 --- /dev/null +++ b/infrastructure/stacks/artefact_management/data.tf @@ -0,0 +1,33 @@ +data "aws_iam_role" "app_github_runner_iam_role" { + name = "${var.repo_name}-${var.app_github_runner_role_name}" +} + +variable "repo_name" { + description = "Repository name" + type = string +} + +variable "app_github_runner_role_name" { + description = "GitHub runner role name for the application" + type = string +} + +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "aws_account_id_dev" { + description = "AWS Account ID for dev environment" + type = string +} + +variable "aws_account_id_test" { + description = "AWS Account ID for test environment" + type = string +} + +variable "aws_account_id_prod" { + description = "AWS Account ID for prod environment" + type = string +} diff --git a/infrastructure/stacks/artefact_management/s3.tf b/infrastructure/stacks/artefact_management/s3.tf new file mode 100644 index 00000000..d2193f03 --- /dev/null +++ b/infrastructure/stacks/artefact_management/s3.tf @@ -0,0 +1,52 @@ +module "artefacts_bucket" { + source = "../../modules/s3" + bucket_name = local.artefacts_bucket +} + + +resource "aws_s3_bucket_policy" "artefacts_bucket_policy" { + depends_on = [module.artefacts_bucket] + bucket = local.artefacts_bucket + policy = data.aws_iam_policy_document.artefacts_bucket_policy.json +} + +data "aws_iam_policy_document" "artefacts_bucket_policy" { + statement { + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id_dev}:role/${var.repo_name}-dev-${var.app_github_runner_role_name}" + ] + } + actions = [ + "s3:ListBucket", + ] + resources = [ + "${module.artefacts_bucket.s3_bucket_arn}" + ] + } + + statement { + principals { + type = "AWS" + identifiers = [ + "${data.aws_iam_role.app_github_runner_iam_role.arn}", + "arn:aws:iam::${var.aws_account_id_prod}:role/${var.repo_name}-${var.app_github_runner_role_name}", + "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-ref-${var.app_github_runner_role_name}", + "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-int-${var.app_github_runner_role_name}", + "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-test-${var.app_github_runner_role_name}", + "arn:aws:iam::${var.aws_account_id_dev}:role/${var.repo_name}-dev-${var.app_github_runner_role_name}" + ] + } + actions = [ + "s3:GetObject", + "s3:GetObjectTagging", + "s3:DeleteObject", + "s3:PutObject", + "s3:PutObjectTagging" + ] + resources = [ + "${module.artefacts_bucket.s3_bucket_arn}/*", + ] + } +} diff --git a/infrastructure/stacks/github_runner/oidc-provider.tf b/infrastructure/stacks/github_runner/oidc-provider.tf new file mode 100644 index 00000000..2d886efb --- /dev/null +++ b/infrastructure/stacks/github_runner/oidc-provider.tf @@ -0,0 +1,9 @@ +resource "aws_iam_openid_connect_provider" "github_provider" { + url = var.oidc_provider_url + client_id_list = [ + var.oidc_client, + ] + thumbprint_list = [ + var.oidc_thumbprint + ] +} diff --git a/infrastructure/stacks/github_runner/variables.tf b/infrastructure/stacks/github_runner/variables.tf index 55f2552d..fca9158e 100644 --- a/infrastructure/stacks/github_runner/variables.tf +++ b/infrastructure/stacks/github_runner/variables.tf @@ -2,3 +2,13 @@ variable "github_org" { description = "The name of the git hub organisation - eg NHSDigital" } + +variable "oidc_provider_url" { + description = "Url of oidc provider" +} +variable "oidc_client" { + description = "Client of oidc provider - eg aws" +} +variable "oidc_thumbprint" { + description = "Thumbprint for oidc provider" +} diff --git a/scripts/workflow/boostrapper.sh b/scripts/workflow/boostrapper.sh index b4b4271b..4afa72d3 100755 --- a/scripts/workflow/boostrapper.sh +++ b/scripts/workflow/boostrapper.sh @@ -10,7 +10,7 @@ set -e # - They are NOT set in this script to avoid details being stored in repo export ACTION="${ACTION:-"apply"}" # default action is plan export AWS_REGION="${AWS_REGION:-"eu-west-2"}" # The AWS region into which you intend to deploy the application (where the terraform bucket will be created) eg eu-west-2 -export ENVIRONMENT="${ENVIRONMENT:-"dev"}" # Identify the environment (one of dev,test,security,preprod or prod) usually part of the account name +export ENVIRONMENT="${ENVIRONMENT:-"mgmt"}" # Identify the environment (one of dev,test,security,preprod or prod) usually part of the account name export PROJECT="${PROJECT:-"saet"}" export TF_VAR_repo_name="${REPOSITORY:-"$(basename -s .git "$(git config --get remote.origin.url)")"}" export TF_VAR_terraform_state_bucket_name="nhse-$ENVIRONMENT-$TF_VAR_repo_name-terraform-state" # globally unique name @@ -123,6 +123,14 @@ function terraform-initialise { function github_runner_stack { # now do account_wide stack for github runner and for oidc provider + # ------------- Step three create thumbprint for github actions ----------- + export HOST=$(curl https://token.actions.githubusercontent.com/.well-known/openid-configuration) + export CERT_URL=$(jq -r '.jwks_uri | split("/")[2]' <<< $HOST) + export THUMBPRINT=$(echo | openssl s_client -servername "$CERT_URL" -showcerts -connect "$CERT_URL":443 2> /dev/null | tac | sed -n '/-----END CERTIFICATE-----/,/-----BEGIN CERTIFICATE-----/p; /-----BEGIN CERTIFICATE-----/q' | tac | openssl x509 -sha1 -fingerprint -noout | sed 's/://g' | awk -F= '{print tolower($2)}') + # ------------- Step four create oidc identity provider, github runner role and policies for that role ----------- + export TF_VAR_oidc_provider_url="https://token.actions.githubusercontent.com" + export TF_VAR_oidc_thumbprint=$THUMBPRINT + export TF_VAR_oidc_client="sts.amazonaws.com" export STACK=github_runner TF_VAR_stack_name=$(echo "$STACK" | tr '_' '-' ) export TF_VAR_stack_name @@ -162,14 +170,14 @@ function github_runner_stack { # init terraform terraform-initialise - if [[ -n "$ACTION" ] && [ "$ACTION" = 'plan' ]] ; then + if [ -n "$ACTION" ] && [ "$ACTION" = 'plan' ] ; then terraform plan \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$COMMON_TF_VARS_FILE \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$STACK_TF_VARS_FILE \ -var-file "$ENVIRONMENTS_DIR/$ENV_TF_VARS_FILE" fi - if [[ -n "$ACTION" ] && [ "$ACTION" = 'apply' ]] ; then + if [ -n "$ACTION" ] && [ "$ACTION" = 'apply' ] ; then terraform apply -auto-approve \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$COMMON_TF_VARS_FILE \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$STACK_TF_VARS_FILE \ @@ -180,7 +188,7 @@ function github_runner_stack { rm -f "$STACK_DIR"/locals.tf rm -f "$STACK_DIR"/provider.tf rm -f "$STACK_DIR"/versions.tf - if [[ $TEMP_STACK_TF_VARS_FILE == 1 ]]; then + if [ $TEMP_STACK_TF_VARS_FILE == 1 ]; then rm "$ROOT_DIR/$INFRASTRUCTURE_DIR/$STACK_TF_VARS_FILE" fi @@ -221,19 +229,19 @@ fi # init terraform terraform-initialise -if [[ -n "$ACTION" ] && [ "$ACTION" = 'plan' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'plan' ] ; then terraform plan \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$COMMON_TF_VARS_FILE \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$STACK_TF_VARS_FILE \ -var-file "$ENVIRONMENTS_DIR/$ENV_TF_VARS_FILE" fi -if [[ -n "$ACTION" ] && [ "$ACTION" = 'apply' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'apply' ] ; then terraform apply -auto-approve \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$COMMON_TF_VARS_FILE \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$STACK_TF_VARS_FILE \ -var-file "$ENVIRONMENTS_DIR/$ENV_TF_VARS_FILE" fi -if [[ -n "$ACTION" ] && [ "$ACTION" = 'destroy' ]] ; then +if [ -n "$ACTION" ] && [ "$ACTION" = 'destroy' ] ; then terraform destroy -auto-approve \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$COMMON_TF_VARS_FILE \ -var-file "$ROOT_DIR"/"$INFRASTRUCTURE_DIR"/$STACK_TF_VARS_FILE \ From 70c46ab1d2a8ff5b539f5fd3919d0ff80f9b5b63 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 11:33:21 +0000 Subject: [PATCH 153/181] Management account set up --- .../action-infrastructure-stack/action.yaml | 1 + .github/workflows/deploy-infrastructure.yaml | 11 ++++++++ ...ipeline-deploy-account-infrastructure.yaml | 28 ++++++++++++++++--- .../stacks/artefact_management/s3.tf | 4 --- scripts/workflow/action-infra-stack.sh | 9 ++++++ 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.github/actions/action-infrastructure-stack/action.yaml b/.github/actions/action-infrastructure-stack/action.yaml index cc1c2f9d..20371b1b 100644 --- a/.github/actions/action-infrastructure-stack/action.yaml +++ b/.github/actions/action-infrastructure-stack/action.yaml @@ -59,6 +59,7 @@ runs: APPLICATION_TAG: ${{ inputs.application_tag }} RELEASE_TAG: ${{ inputs.release_tag }} COMMIT_HASH: ${{ inputs.commit_hash }} + AWS_ACCOUNT_ID_DEV: ${{ inputs.account_id_dev }} id: "action_stack" shell: bash run: | diff --git a/.github/workflows/deploy-infrastructure.yaml b/.github/workflows/deploy-infrastructure.yaml index a61f32a2..116f61f2 100644 --- a/.github/workflows/deploy-infrastructure.yaml +++ b/.github/workflows/deploy-infrastructure.yaml @@ -59,6 +59,16 @@ on: description: "The type of permissions (e.g., account, app)" required: true type: string + secrets: + ACCOUNT_ID: + description: "AWS account ID for credentials" + required: true + MGMT_ACCOUNT_ID: + description: "AWS management account ID for credentials" + required: true + ACCOUNT_ID_DEV: + description: "AWS dev account ID for credentials" + required: true outputs: plan_result: description: "The Terraform plan output" @@ -110,6 +120,7 @@ jobs: release_tag: ${{ inputs.release_tag }} commit_hash: ${{ inputs.commit_hash }} mgmt_account_id: ${{ secrets.MGMT_ACCOUNT_ID }} + account_id_dev: ${{ secrets.ACCOUNT_ID_DEV }} - name: "Upload Terraform Plan Artifact" uses: actions/upload-artifact@v6 diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 82daba25..25ea0556 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -16,6 +16,7 @@ on: - "infrastructure/stacks/account_security/**" - "infrastructure/modules/shield/**" - "infrastructure/stacks/account_wide/**" + - "infrastructure/stacks/artefact_management/**" workflow_run: workflows: ["Pipeline Deploy Policies Infrastructure"] types: @@ -46,6 +47,16 @@ on: description: "Deployment environment" required: true type: string + secrets: + ACCOUNT_ID: + description: "AWS account ID for credentials" + required: true + MGMT_ACCOUNT_ID: + description: "Management AWS account ID for credentials" + required: true + ACCOUNT_ID_DEV: + description: "AWS dev account ID" + required: true concurrency: group: account-infrastructure-${{ github.ref }} @@ -66,10 +77,13 @@ jobs: tag: ${{ inputs.tag }} environment: ${{ needs.metadata.outputs.environment }} workspace: "default" - stacks: "['terraform_management','account_security','account_wide']" + stacks: "['terraform_management','account_security','account_wide','artefact_management']" type: account build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} plan-infrastructure: name: "Plan ${{ matrix.name }} infrastructure deployment for ${{ matrix.environment }}" @@ -100,7 +114,10 @@ jobs: stacks: ${{ matrix.stacks }} action: plan type: account - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} manual-approval: name: "Manual approval for ${{ needs.metadata.outputs.environment }} infrastructure deployment" @@ -143,4 +160,7 @@ jobs: stacks: ${{ matrix.stacks }} action: apply type: account - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} diff --git a/infrastructure/stacks/artefact_management/s3.tf b/infrastructure/stacks/artefact_management/s3.tf index d2193f03..d406b9ea 100644 --- a/infrastructure/stacks/artefact_management/s3.tf +++ b/infrastructure/stacks/artefact_management/s3.tf @@ -31,10 +31,6 @@ data "aws_iam_policy_document" "artefacts_bucket_policy" { type = "AWS" identifiers = [ "${data.aws_iam_role.app_github_runner_iam_role.arn}", - "arn:aws:iam::${var.aws_account_id_prod}:role/${var.repo_name}-${var.app_github_runner_role_name}", - "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-ref-${var.app_github_runner_role_name}", - "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-int-${var.app_github_runner_role_name}", - "arn:aws:iam::${var.aws_account_id_test}:role/${var.repo_name}-test-${var.app_github_runner_role_name}", "arn:aws:iam::${var.aws_account_id_dev}:role/${var.repo_name}-dev-${var.app_github_runner_role_name}" ] } diff --git a/scripts/workflow/action-infra-stack.sh b/scripts/workflow/action-infra-stack.sh index 0b4de960..5d6296b3 100644 --- a/scripts/workflow/action-infra-stack.sh +++ b/scripts/workflow/action-infra-stack.sh @@ -164,6 +164,15 @@ terraform-initialise terraform workspace select -or-create "$WORKSPACE" +if [ "$STACK" = "artefact_management" ] ; then + echo "Exporting splunk secrets needed for Firehose" + if [ -z "$ACCOUNT_ID_DEV" ] ; then + echo "Account_Id_Dev environment variables must be set for artefact_management stack" + exit 1 + fi + export TF_VAR_aws_account_id_dev=$ACCOUNT_ID_DEV +fi + # plan if [ -n "$ACTION" ] && [ "$ACTION" = 'plan' ] ; then terraform plan -out $STACK.tfplan \ From c4235edb92ab3b82a2cbec0af155673bf7533ee0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 11:42:37 +0000 Subject: [PATCH 154/181] Management account set up --- .github/workflows/pipeline-deploy-application.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 87cab239..50b0db27 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -47,7 +47,10 @@ jobs: stacks: "['triage']" type: app build_timestamp: ${{ needs.metadata.outputs.build_timestamp }} - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} # build-services: # name: "Build ${{ matrix.name }}" From 89472f6557a421e99e1ecb4471d655f33f004084 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 11:46:27 +0000 Subject: [PATCH 155/181] Management account set up --- .github/workflows/quality-checks.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/quality-checks.yaml b/.github/workflows/quality-checks.yaml index ff573363..33cd2f06 100644 --- a/.github/workflows/quality-checks.yaml +++ b/.github/workflows/quality-checks.yaml @@ -40,6 +40,16 @@ on: description: "The build timestamp" required: false type: string + secrets: + ACCOUNT_ID: + description: "AWS account ID for credentials" + required: true + MGMT_ACCOUNT_ID: + description: "Management AWS account ID for credentials" + required: true + ACCOUNT_ID_DEV: + description: "AWS dev account ID" + required: true jobs: scan-secrets: From 0f2ebdf618923af40681ba83a3d2b4dc0f5d1dd8 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 12:22:08 +0000 Subject: [PATCH 156/181] Management account set up --- .../deploy-application-infrastructure.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-application-infrastructure.yaml b/.github/workflows/deploy-application-infrastructure.yaml index 9982acc1..ff3b6929 100644 --- a/.github/workflows/deploy-application-infrastructure.yaml +++ b/.github/workflows/deploy-application-infrastructure.yaml @@ -51,6 +51,16 @@ on: required: false default: "['triage']" type: string + secrets: + ACCOUNT_ID: + description: "AWS account ID for credentials" + required: true + MGMT_ACCOUNT_ID: + description: "AWS management account ID for credentials" + required: true + ACCOUNT_ID_DEV: + description: "AWS dev account ID for credentials" + required: true outputs: plan_result: description: "The Terraform plan output" @@ -78,7 +88,10 @@ jobs: action: plan type: ${{ inputs.type }} workflow_timeout: ${{ inputs.workflow_timeout }} - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} manual-approval-application-infra: name: "Manual approval for deployment of application infrastructure to the ${{ inputs.environment }} environment" From 5dca3c02e6d1679e0329b37720a9c40e89b05c99 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 12:28:07 +0000 Subject: [PATCH 157/181] Management account set up --- .github/workflows/pipeline-deploy-application.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index 50b0db27..a6d02e7d 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -99,7 +99,10 @@ jobs: tag: ${{ inputs.tag }} commit_hash: ${{ needs.metadata.outputs.commit_hash }} workflow_timeout: 30 - secrets: inherit + secrets: + ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} + MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} + ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} check-pipeline-status: name: "Check Pipeline Status" From a2ed585cb677a7fb2e6a0a965a7248fc9d662a1e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 14:01:03 +0000 Subject: [PATCH 158/181] Management account set up --- .../stacks/artefact_management/data.tf | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf index d60555f9..4186a0dc 100644 --- a/infrastructure/stacks/artefact_management/data.tf +++ b/infrastructure/stacks/artefact_management/data.tf @@ -2,25 +2,25 @@ data "aws_iam_role" "app_github_runner_iam_role" { name = "${var.repo_name}-${var.app_github_runner_role_name}" } -variable "repo_name" { - description = "Repository name" - type = string -} +# variable "repo_name" { +# description = "Repository name" +# type = string +# } -variable "app_github_runner_role_name" { - description = "GitHub runner role name for the application" - type = string -} +# variable "app_github_runner_role_name" { +# description = "GitHub runner role name for the application" +# type = string +# } -variable "aws_region" { - description = "AWS region" - type = string -} +# variable "aws_region" { +# description = "AWS region" +# type = string +# } -variable "aws_account_id_dev" { - description = "AWS Account ID for dev environment" - type = string -} +# variable "aws_account_id_dev" { +# description = "AWS Account ID for dev environment" +# type = string +# } variable "aws_account_id_test" { description = "AWS Account ID for test environment" From 33dd40c2cb07a58f4b37f337f9e93505a1f660ed Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 14:15:24 +0000 Subject: [PATCH 159/181] Management account set up --- .../action-infrastructure-stack/action.yaml | 3 +++ .../stacks/artefact_management/data.tf | 23 ++++--------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/actions/action-infrastructure-stack/action.yaml b/.github/actions/action-infrastructure-stack/action.yaml index 20371b1b..437b1993 100644 --- a/.github/actions/action-infrastructure-stack/action.yaml +++ b/.github/actions/action-infrastructure-stack/action.yaml @@ -28,6 +28,9 @@ inputs: mgmt_account_id: description: "The management account ID for the action" required: true + account_id_dev: + description: "AWS dev account ID" + required: true release_tag: description: "The release tag identifying the timeline in the repository to deploy from" required: false diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf index 4186a0dc..38cb68ef 100644 --- a/infrastructure/stacks/artefact_management/data.tf +++ b/infrastructure/stacks/artefact_management/data.tf @@ -2,25 +2,10 @@ data "aws_iam_role" "app_github_runner_iam_role" { name = "${var.repo_name}-${var.app_github_runner_role_name}" } -# variable "repo_name" { -# description = "Repository name" -# type = string -# } - -# variable "app_github_runner_role_name" { -# description = "GitHub runner role name for the application" -# type = string -# } - -# variable "aws_region" { -# description = "AWS region" -# type = string -# } - -# variable "aws_account_id_dev" { -# description = "AWS Account ID for dev environment" -# type = string -# } +variable "aws_account_id_dev" { + description = "AWS Account ID for dev environment" + type = string +} variable "aws_account_id_test" { description = "AWS Account ID for test environment" From 437ceb28d80d83c750cf6a425333bf4ac4a645a0 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 14:22:33 +0000 Subject: [PATCH 160/181] Management account set up --- scripts/workflow/action-infra-stack.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/workflow/action-infra-stack.sh b/scripts/workflow/action-infra-stack.sh index 5d6296b3..e2162a1e 100644 --- a/scripts/workflow/action-infra-stack.sh +++ b/scripts/workflow/action-infra-stack.sh @@ -165,12 +165,12 @@ terraform-initialise terraform workspace select -or-create "$WORKSPACE" if [ "$STACK" = "artefact_management" ] ; then - echo "Exporting splunk secrets needed for Firehose" - if [ -z "$ACCOUNT_ID_DEV" ] ; then - echo "Account_Id_Dev environment variables must be set for artefact_management stack" + echo "Exporting account ID for artefact_management stack" + if [ -z "$AWS_ACCOUNT_ID_DEV" ] ; then + echo "AWS_ACCOUNT_ID_DEV environment variable must be set for artefact_management stack" exit 1 fi - export TF_VAR_aws_account_id_dev=$ACCOUNT_ID_DEV + export TF_VAR_aws_account_id_dev=$AWS_ACCOUNT_ID_DEV fi # plan From cc664f8d8803e743987c8253fcf7cd86fd78a0df Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 15:34:49 +0000 Subject: [PATCH 161/181] Management account set up --- .github/actions/action-infrastructure-stack/action.yaml | 4 ++-- .github/workflows/deploy-application-infrastructure.yaml | 4 ++-- .github/workflows/deploy-infrastructure.yaml | 4 ++-- .../workflows/pipeline-deploy-account-infrastructure.yaml | 8 ++++---- .github/workflows/pipeline-deploy-application.yaml | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/actions/action-infrastructure-stack/action.yaml b/.github/actions/action-infrastructure-stack/action.yaml index 437b1993..398b7374 100644 --- a/.github/actions/action-infrastructure-stack/action.yaml +++ b/.github/actions/action-infrastructure-stack/action.yaml @@ -28,7 +28,7 @@ inputs: mgmt_account_id: description: "The management account ID for the action" required: true - account_id_dev: + AWS_ACCOUNT_ID_DEV: description: "AWS dev account ID" required: true release_tag: @@ -62,7 +62,7 @@ runs: APPLICATION_TAG: ${{ inputs.application_tag }} RELEASE_TAG: ${{ inputs.release_tag }} COMMIT_HASH: ${{ inputs.commit_hash }} - AWS_ACCOUNT_ID_DEV: ${{ inputs.account_id_dev }} + AWS_AWS_ACCOUNT_ID_DEV: ${{ inputs.AWS_ACCOUNT_ID_DEV }} id: "action_stack" shell: bash run: | diff --git a/.github/workflows/deploy-application-infrastructure.yaml b/.github/workflows/deploy-application-infrastructure.yaml index ff3b6929..25b440c5 100644 --- a/.github/workflows/deploy-application-infrastructure.yaml +++ b/.github/workflows/deploy-application-infrastructure.yaml @@ -58,7 +58,7 @@ on: MGMT_ACCOUNT_ID: description: "AWS management account ID for credentials" required: true - ACCOUNT_ID_DEV: + AWS_ACCOUNT_ID_DEV: description: "AWS dev account ID for credentials" required: true outputs: @@ -91,7 +91,7 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} manual-approval-application-infra: name: "Manual approval for deployment of application infrastructure to the ${{ inputs.environment }} environment" diff --git a/.github/workflows/deploy-infrastructure.yaml b/.github/workflows/deploy-infrastructure.yaml index 116f61f2..394afcc1 100644 --- a/.github/workflows/deploy-infrastructure.yaml +++ b/.github/workflows/deploy-infrastructure.yaml @@ -66,7 +66,7 @@ on: MGMT_ACCOUNT_ID: description: "AWS management account ID for credentials" required: true - ACCOUNT_ID_DEV: + AWS_ACCOUNT_ID_DEV: description: "AWS dev account ID for credentials" required: true outputs: @@ -120,7 +120,7 @@ jobs: release_tag: ${{ inputs.release_tag }} commit_hash: ${{ inputs.commit_hash }} mgmt_account_id: ${{ secrets.MGMT_ACCOUNT_ID }} - account_id_dev: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} - name: "Upload Terraform Plan Artifact" uses: actions/upload-artifact@v6 diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 25ea0556..5f36d1f3 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -54,7 +54,7 @@ on: MGMT_ACCOUNT_ID: description: "Management AWS account ID for credentials" required: true - ACCOUNT_ID_DEV: + AWS_ACCOUNT_ID_DEV: description: "AWS dev account ID" required: true @@ -83,7 +83,7 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} plan-infrastructure: name: "Plan ${{ matrix.name }} infrastructure deployment for ${{ matrix.environment }}" @@ -117,7 +117,7 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} manual-approval: name: "Manual approval for ${{ needs.metadata.outputs.environment }} infrastructure deployment" @@ -163,4 +163,4 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index a6d02e7d..f7137bbb 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -50,7 +50,7 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} # build-services: # name: "Build ${{ matrix.name }}" @@ -102,7 +102,7 @@ jobs: secrets: ACCOUNT_ID: ${{ secrets.ACCOUNT_ID }} MGMT_ACCOUNT_ID: ${{ secrets.MGMT_ACCOUNT_ID }} - ACCOUNT_ID_DEV: ${{ secrets.ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} check-pipeline-status: name: "Check Pipeline Status" From 2cd4350b77e76d171b8845b8110162b9ee94f313 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 15:39:20 +0000 Subject: [PATCH 162/181] Management account set up --- .github/workflows/quality-checks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/quality-checks.yaml b/.github/workflows/quality-checks.yaml index 33cd2f06..62f1fbdb 100644 --- a/.github/workflows/quality-checks.yaml +++ b/.github/workflows/quality-checks.yaml @@ -47,7 +47,7 @@ on: MGMT_ACCOUNT_ID: description: "Management AWS account ID for credentials" required: true - ACCOUNT_ID_DEV: + AWS_ACCOUNT_ID_DEV: description: "AWS dev account ID" required: true From 9b26f4469305487da11162457d8ef9c214b2781b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 15:45:29 +0000 Subject: [PATCH 163/181] Management account set up --- infrastructure/stacks/account_wide/vpc.tf | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 26a5c95e..9e784f74 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -10,22 +10,12 @@ module "vpc" { single_nat_gateway = var.single_nat_gateway one_nat_gateway_per_az = var.one_nat_gateway_per_az - create_database_subnet_group = var.create_database_subnet_group - create_database_subnet_route_table = var.create_database_route_table - create_database_internet_gateway_route = var.create_database_internet_gateway_route - create_database_nat_gateway_route = var.create_database_nat_gateway_route - database_subnet_group_name = "${local.account_prefix}-database-subnet-group" - azs = slice(data.aws_availability_zones.available_azs.names, 0, 3) public_subnets = local.public_subnets private_subnets = local.private_subnets #database_subnets = local.database_subnets # NACL configuration - database_dedicated_network_acl = var.database_dedicated_network_acl - database_inbound_acl_rules = local.network_acls["default_inbound"] - database_outbound_acl_rules = local.network_acls["default_outbound"] - private_dedicated_network_acl = var.private_dedicated_network_acl private_inbound_acl_rules = local.network_acls["default_inbound"] private_outbound_acl_rules = local.network_acls["default_outbound"] From 45b07ec2d048a4a003f26235182c914692c27f3e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 15:46:40 +0000 Subject: [PATCH 164/181] Management account set up --- infrastructure/stacks/account_wide/vpc.tf | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/infrastructure/stacks/account_wide/vpc.tf b/infrastructure/stacks/account_wide/vpc.tf index 9e784f74..68977b25 100644 --- a/infrastructure/stacks/account_wide/vpc.tf +++ b/infrastructure/stacks/account_wide/vpc.tf @@ -147,24 +147,3 @@ resource "aws_flow_log" "private_subnet_flow_log_s3" { } subnet_id = module.vpc.private_subnets[count.index] } - -# resource "aws_flow_log" "database_subnet_flow_log_s3" { -# count = length(local.database_subnets) -# log_destination = module.subnet_flow_logs_s3_bucket.s3_bucket_arn -# log_destination_type = var.flow_log_destination_type -# traffic_type = "REJECT" -# destination_options { -# per_hour_partition = true -# } -# subnet_id = module.vpc.database_subnets[count.index] -# } - -# Add a CIDR Range tag to the private subnets for filtering -resource "aws_ec2_tag" "private_subnet_tags" { - count = length(module.vpc.private_subnets) - - resource_id = data.aws_subnet.vpc_private_subnets_by_count[count.index].id - key = "CidrRange" - value = split("/", data.aws_subnet.vpc_private_subnets_by_count[count.index].cidr_block)[1] -} - From 411cfef623b2d9f49d4b725d6bbae471ef8974f2 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 15:52:15 +0000 Subject: [PATCH 165/181] Management account set up --- .github/actions/action-infrastructure-stack/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/action-infrastructure-stack/action.yaml b/.github/actions/action-infrastructure-stack/action.yaml index 398b7374..914a278b 100644 --- a/.github/actions/action-infrastructure-stack/action.yaml +++ b/.github/actions/action-infrastructure-stack/action.yaml @@ -62,7 +62,7 @@ runs: APPLICATION_TAG: ${{ inputs.application_tag }} RELEASE_TAG: ${{ inputs.release_tag }} COMMIT_HASH: ${{ inputs.commit_hash }} - AWS_AWS_ACCOUNT_ID_DEV: ${{ inputs.AWS_ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ inputs.AWS_ACCOUNT_ID_DEV }} id: "action_stack" shell: bash run: | From 2acf3232fa9d3109e8062ec6974cc7c2b78d459f Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 16:05:41 +0000 Subject: [PATCH 166/181] Management account set up --- .github/actions/action-infrastructure-stack/action.yaml | 6 +++--- .github/workflows/deploy-infrastructure.yaml | 2 +- .github/workflows/quality-checks.yaml | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/actions/action-infrastructure-stack/action.yaml b/.github/actions/action-infrastructure-stack/action.yaml index 914a278b..4320b1e3 100644 --- a/.github/actions/action-infrastructure-stack/action.yaml +++ b/.github/actions/action-infrastructure-stack/action.yaml @@ -28,9 +28,9 @@ inputs: mgmt_account_id: description: "The management account ID for the action" required: true - AWS_ACCOUNT_ID_DEV: + account_id_dev: description: "AWS dev account ID" - required: true + required: false release_tag: description: "The release tag identifying the timeline in the repository to deploy from" required: false @@ -62,7 +62,7 @@ runs: APPLICATION_TAG: ${{ inputs.application_tag }} RELEASE_TAG: ${{ inputs.release_tag }} COMMIT_HASH: ${{ inputs.commit_hash }} - AWS_ACCOUNT_ID_DEV: ${{ inputs.AWS_ACCOUNT_ID_DEV }} + AWS_ACCOUNT_ID_DEV: ${{ inputs.account_id_dev }} id: "action_stack" shell: bash run: | diff --git a/.github/workflows/deploy-infrastructure.yaml b/.github/workflows/deploy-infrastructure.yaml index 394afcc1..72723f54 100644 --- a/.github/workflows/deploy-infrastructure.yaml +++ b/.github/workflows/deploy-infrastructure.yaml @@ -120,7 +120,7 @@ jobs: release_tag: ${{ inputs.release_tag }} commit_hash: ${{ inputs.commit_hash }} mgmt_account_id: ${{ secrets.MGMT_ACCOUNT_ID }} - AWS_ACCOUNT_ID_DEV: ${{ secrets.AWS_ACCOUNT_ID_DEV }} + account_id_dev: ${{ secrets.AWS_ACCOUNT_ID_DEV }} - name: "Upload Terraform Plan Artifact" uses: actions/upload-artifact@v6 diff --git a/.github/workflows/quality-checks.yaml b/.github/workflows/quality-checks.yaml index 62f1fbdb..54e4b329 100644 --- a/.github/workflows/quality-checks.yaml +++ b/.github/workflows/quality-checks.yaml @@ -173,7 +173,9 @@ jobs: workspace: ${{ inputs.workspace }} stack: ${{ matrix.stack }} action: validate + project: saet mgmt_account_id: ${{ secrets.MGMT_ACCOUNT_ID }} + account_id_dev: ${{ secrets.AWS_ACCOUNT_ID_DEV }} check-terraform-format: name: "Check Terraform format" From 50999d4a3586a777386379db1a33ce044e562a3a Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Thu, 19 Feb 2026 16:11:01 +0000 Subject: [PATCH 167/181] Management account set up --- infrastructure/common/common-variables.tf | 5 +++++ infrastructure/common/locals.tf | 1 + 2 files changed, 6 insertions(+) diff --git a/infrastructure/common/common-variables.tf b/infrastructure/common/common-variables.tf index fc50d767..153b1d7a 100644 --- a/infrastructure/common/common-variables.tf +++ b/infrastructure/common/common-variables.tf @@ -71,6 +71,11 @@ variable "app_github_runner_role_name" { default = "app-github-runner" } +variable "artefacts_bucket_name" { + description = "Artefacts S3 bucket name" + type = string +} + variable "stack_name" { description = "The hyphenated version of the stack name used in names of resources defined in that stack" type = string diff --git a/infrastructure/common/locals.tf b/infrastructure/common/locals.tf index 6d6bf42e..e5a9cd2a 100644 --- a/infrastructure/common/locals.tf +++ b/infrastructure/common/locals.tf @@ -1,6 +1,7 @@ locals { account_id = data.aws_caller_identity.current.id workspace_suffix = "${terraform.workspace}" == "default" ? "" : "-${terraform.workspace}" + artefacts_bucket = "${var.repo_name}-mgmt-${var.artefacts_bucket_name}" project_prefix = "${var.project}-${var.environment}" resource_prefix = "${local.project_prefix}-${var.stack_name}" account_prefix = "${var.repo_name}-${var.environment}" From d9c39095395bc203f5b54a973d20a6f723a6ba21 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 09:55:01 +0000 Subject: [PATCH 168/181] Fixing github runner in mgmt account --- infrastructure/stacks/github_runner/iam_role.tf | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/infrastructure/stacks/github_runner/iam_role.tf b/infrastructure/stacks/github_runner/iam_role.tf index c988f4ac..a2c0577f 100644 --- a/infrastructure/stacks/github_runner/iam_role.tf +++ b/infrastructure/stacks/github_runner/iam_role.tf @@ -25,8 +25,10 @@ resource "aws_iam_role" "account_github_runner_role" { }, "Action":"sts:AssumeRoleWithWebIdentity", "Condition":{ - "ForAllValues:StringLike":{ - "token.actions.githubusercontent.com:sub":"repo:${var.github_org}/${var.repo_name}:*", + "StringLike":{ + "token.actions.githubusercontent.com:sub":"repo:${var.github_org}/${var.repo_name}:*" + }, + "StringEquals":{ "token.actions.githubusercontent.com:aud":"sts.amazonaws.com" } } @@ -50,8 +52,10 @@ resource "aws_iam_role" "app_github_runner_role" { }, "Action":"sts:AssumeRoleWithWebIdentity", "Condition":{ - "ForAllValues:StringLike":{ - "token.actions.githubusercontent.com:sub":"repo:${var.github_org}/${var.repo_name}:*", + "StringLike":{ + "token.actions.githubusercontent.com:sub":"repo:${var.github_org}/${var.repo_name}:*" + }, + "StringEquals":{ "token.actions.githubusercontent.com:aud":"sts.amazonaws.com" } } From 4a293e2c9021d500f2a22b2d80b5f9fd02d992aa Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 10:05:52 +0000 Subject: [PATCH 169/181] Fixing github runner in mgmt account --- infrastructure/stacks/artefact_management/data.tf | 2 +- infrastructure/stacks/github_runner/iam_role.tf | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf index 38cb68ef..eb50e3e2 100644 --- a/infrastructure/stacks/artefact_management/data.tf +++ b/infrastructure/stacks/artefact_management/data.tf @@ -1,5 +1,5 @@ data "aws_iam_role" "app_github_runner_iam_role" { - name = "${var.repo_name}-${var.app_github_runner_role_name}" + name = "${var.repo_name}-${var.environment}-${var.app_github_runner_role_name}" } variable "aws_account_id_dev" { diff --git a/infrastructure/stacks/github_runner/iam_role.tf b/infrastructure/stacks/github_runner/iam_role.tf index a2c0577f..cd3f18fe 100644 --- a/infrastructure/stacks/github_runner/iam_role.tf +++ b/infrastructure/stacks/github_runner/iam_role.tf @@ -12,7 +12,7 @@ locals { } resource "aws_iam_role" "account_github_runner_role" { - name = "${var.repo_name}${var.environment != "mgmt" ? "-${var.environment}" : ""}-${var.account_github_runner_role_name}" + name = "${var.repo_name}-${var.environment}-${var.account_github_runner_role_name}" assume_role_policy = < Date: Fri, 20 Feb 2026 11:20:41 +0000 Subject: [PATCH 170/181] Fixing github runner in mgmt account --- .../stacks/github_runner/iam_role.tf | 98 +++++++++---------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/infrastructure/stacks/github_runner/iam_role.tf b/infrastructure/stacks/github_runner/iam_role.tf index cd3f18fe..44e5ec44 100644 --- a/infrastructure/stacks/github_runner/iam_role.tf +++ b/infrastructure/stacks/github_runner/iam_role.tf @@ -12,7 +12,7 @@ locals { } resource "aws_iam_role" "account_github_runner_role" { - name = "${var.repo_name}-${var.environment}-${var.account_github_runner_role_name}" + name = "${var.repo_name}${var.environment != "mgmt" ? "-${var.environment}" : ""}-${var.account_github_runner_role_name}" assume_role_policy = < Date: Fri, 20 Feb 2026 11:45:48 +0000 Subject: [PATCH 171/181] Fixing github runner in mgmt account --- .github/actions/configure-credentials/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/configure-credentials/action.yaml b/.github/actions/configure-credentials/action.yaml index 3dd4915f..c0c34fef 100644 --- a/.github/actions/configure-credentials/action.yaml +++ b/.github/actions/configure-credentials/action.yaml @@ -23,6 +23,6 @@ runs: - name: configure aws credentials uses: aws-actions/configure-aws-credentials@v5.1.1 with: - role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ github.event.repository.name }}-${{ inputs.environment }}-${{ inputs.type }}-github-runner + role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ github.event.repository.name }}${{ inputs.environment != 'mgmt' && format('-{0}', inputs.environment) || '' }}-${{ inputs.type }}-github-runner role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ inputs.aws_region }} From f749a93c5f3a3788117dd309fd008cee042b9fcf Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 11:50:13 +0000 Subject: [PATCH 172/181] Fixing github runner in mgmt account --- infrastructure/common.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/common.tfvars b/infrastructure/common.tfvars index db2d7534..53a67eea 100644 --- a/infrastructure/common.tfvars +++ b/infrastructure/common.tfvars @@ -8,7 +8,7 @@ project_owner = "nhs-uec" # service_category = "bronze" team_owner = "saet" -# artefacts_bucket_name = "artefacts-bucket" +artefacts_bucket_name = "artefacts-bucket" s3_logging_bucket_name = "s3-access-logs" # rds_port = 5432 From 4eae5eda421a59f0c6f83ba2868156e0eb675231 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:03:57 +0000 Subject: [PATCH 173/181] Fixing github runner in mgmt account --- infrastructure/stacks/account_wide/variables.tf | 8 ++++---- .../stacks/artefact_management/data.tf | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/infrastructure/stacks/account_wide/variables.tf b/infrastructure/stacks/account_wide/variables.tf index 87906e60..0d43be40 100644 --- a/infrastructure/stacks/account_wide/variables.tf +++ b/infrastructure/stacks/account_wide/variables.tf @@ -24,10 +24,10 @@ variable "gateway_vpc_endpoint_type" { default = "Gateway" } -variable "database_dedicated_network_acl" { - description = "Whether to use dedicated network ACL (not default) and custom rules for database subnets" - type = bool -} +# variable "database_dedicated_network_acl" { +# description = "Whether to use dedicated network ACL (not default) and custom rules for database subnets" +# type = bool +# } variable "private_dedicated_network_acl" { description = "Whether to use dedicated network ACL (not default) and custom rules for private subnets" diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf index eb50e3e2..4d0c1723 100644 --- a/infrastructure/stacks/artefact_management/data.tf +++ b/infrastructure/stacks/artefact_management/data.tf @@ -7,12 +7,12 @@ variable "aws_account_id_dev" { type = string } -variable "aws_account_id_test" { - description = "AWS Account ID for test environment" - type = string -} +# variable "aws_account_id_test" { +# description = "AWS Account ID for test environment" +# type = string +# } -variable "aws_account_id_prod" { - description = "AWS Account ID for prod environment" - type = string -} +# variable "aws_account_id_prod" { +# description = "AWS Account ID for prod environment" +# type = string +# } From 143aa1497410086f34e6acb7514fc8691bc4f68c Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:17:36 +0000 Subject: [PATCH 174/181] Fixing github runner in mgmt account --- infrastructure/stacks/account_wide/variables.tf | 5 ----- infrastructure/stacks/artefact_management/data.tf | 2 +- infrastructure/triage.tfvars | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/infrastructure/stacks/account_wide/variables.tf b/infrastructure/stacks/account_wide/variables.tf index 0d43be40..79864000 100644 --- a/infrastructure/stacks/account_wide/variables.tf +++ b/infrastructure/stacks/account_wide/variables.tf @@ -24,11 +24,6 @@ variable "gateway_vpc_endpoint_type" { default = "Gateway" } -# variable "database_dedicated_network_acl" { -# description = "Whether to use dedicated network ACL (not default) and custom rules for database subnets" -# type = bool -# } - variable "private_dedicated_network_acl" { description = "Whether to use dedicated network ACL (not default) and custom rules for private subnets" type = bool diff --git a/infrastructure/stacks/artefact_management/data.tf b/infrastructure/stacks/artefact_management/data.tf index 4d0c1723..9aa3fd5f 100644 --- a/infrastructure/stacks/artefact_management/data.tf +++ b/infrastructure/stacks/artefact_management/data.tf @@ -1,5 +1,5 @@ data "aws_iam_role" "app_github_runner_iam_role" { - name = "${var.repo_name}-${var.environment}-${var.app_github_runner_role_name}" + name = "${var.repo_name}-${var.app_github_runner_role_name}" } variable "aws_account_id_dev" { diff --git a/infrastructure/triage.tfvars b/infrastructure/triage.tfvars index ffa67283..511daef8 100644 --- a/infrastructure/triage.tfvars +++ b/infrastructure/triage.tfvars @@ -1,5 +1,5 @@ #Lambda -mem_size = 512 +mem_size = 1024 runtime = "python3.13" s3_key = "lambda_function.zip" processor_lambda_name = "pathways-lambda-processor" From 3e9e8665e60751daef54cb8c8d0621e3e6e2c899 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:33:49 +0000 Subject: [PATCH 175/181] Fixing github runner in mgmt account --- .github/workflows/pipeline-deploy-account-infrastructure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 5f36d1f3..b41829b4 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -151,7 +151,7 @@ jobs: environment: ${{ needs.metadata.outputs.environment }} - name: "mgmt" environment: ${{ needs.metadata.outputs.mgmt_environment }} - stacks: "['terraform_management','account_wide','artefact_management']" + stacks: "['terraform_management','account_security','artefact_management']" uses: ./.github/workflows/deploy-infrastructure.yaml with: environment: ${{ matrix.environment }} From aa6a7c34cae62a0922a3e734864fda64f0da12f1 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:41:15 +0000 Subject: [PATCH 176/181] Fixing github runner in mgmt account --- infrastructure/stacks/triage/lambda.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index d29f54ae..d98b606b 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -5,7 +5,7 @@ module "s3lambda" { function_name = "${local.resource_prefix}-s3-Lambda" policy_jsons = [aws_iam_policy.s3_access.policy, aws_iam_policy.ddb_access.policy] handler = "s3_configurator.handler" - s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id + s3_bucket_name = module.artefacts_bucket.s3_bucket_id runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size @@ -33,7 +33,7 @@ module "apiglambda" { function_name = "${local.resource_prefix}-apig-Lambda" policy_jsons = [aws_iam_policy.ddb_access.policy, aws_iam_policy.s3_access.policy] handler = "api_gateway_configurator.handler" - s3_bucket_name = module.pathway_artifact_bucket.s3_bucket_id + s3_bucket_name = module.artefacts_bucket.s3_bucket_id runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size From 45f95bd1c2e84ee234379a747084def2bfb16d8d Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:48:01 +0000 Subject: [PATCH 177/181] Fixing github runner in mgmt account --- infrastructure/stacks/triage/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index d98b606b..46374f7d 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -5,7 +5,7 @@ module "s3lambda" { function_name = "${local.resource_prefix}-s3-Lambda" policy_jsons = [aws_iam_policy.s3_access.policy, aws_iam_policy.ddb_access.policy] handler = "s3_configurator.handler" - s3_bucket_name = module.artefacts_bucket.s3_bucket_id + s3_bucket_name = local.artefacts_bucket runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size From b1f7cb2b8a5819d6d3e957c09e749325b226200e Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 12:49:54 +0000 Subject: [PATCH 178/181] Fixing github runner in mgmt account --- infrastructure/stacks/triage/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/stacks/triage/lambda.tf b/infrastructure/stacks/triage/lambda.tf index 46374f7d..ebaae372 100644 --- a/infrastructure/stacks/triage/lambda.tf +++ b/infrastructure/stacks/triage/lambda.tf @@ -33,7 +33,7 @@ module "apiglambda" { function_name = "${local.resource_prefix}-apig-Lambda" policy_jsons = [aws_iam_policy.ddb_access.policy, aws_iam_policy.s3_access.policy] handler = "api_gateway_configurator.handler" - s3_bucket_name = module.artefacts_bucket.s3_bucket_id + s3_bucket_name = local.artefacts_bucket runtime = var.runtime s3_key = var.s3_key memory_size = var.mem_size From 0ee52bdddb01765f4c965f9589f7d0902b26e166 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 13:01:42 +0000 Subject: [PATCH 179/181] Fixing github runner in mgmt account --- .github/workflows/pipeline-deploy-account-infrastructure.yaml | 4 ++-- .github/workflows/pipeline-deploy-policies.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index b41829b4..1c607d59 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -121,7 +121,7 @@ jobs: manual-approval: name: "Manual approval for ${{ needs.metadata.outputs.environment }} infrastructure deployment" - if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up') && (needs.plan-infrastructure.outputs.plan_result == 'true') }} + if: ${{ github.ref == 'refs/heads/main' && (needs.plan-infrastructure.outputs.plan_result == 'true') }} needs: - metadata - plan-infrastructure @@ -136,7 +136,7 @@ jobs: concurrency: group: "${{ matrix.environment }}-default-${{ matrix.name }}-${{matrix.stacks}}" cancel-in-progress: false - if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up' }} + if: ${{ github.ref == 'refs/heads/main' }} needs: - metadata - manual-approval diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index 53815189..716f82ad 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -88,7 +88,7 @@ jobs: manual-approval-permissions: name: "Manual approval for ${{ needs.metadata.outputs.environment }} permissions infrastructure deployment" - if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up') && (needs.plan-permissions-infrastructure.outputs.plan_result == 'true') }} + if: ${{ github.ref == 'refs/heads/main' && (needs.plan-permissions-infrastructure.outputs.plan_result == 'true') }} needs: - metadata - plan-permissions-infrastructure @@ -103,7 +103,7 @@ jobs: concurrency: group: "${{ matrix.environment }}-default-${{ matrix.name }}-permissions-${{matrix.stacks}}" cancel-in-progress: false - if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/task/SAET-0000_Baseline_set_up' }} + if: ${{ github.ref == 'refs/heads/main' }} needs: - metadata - manual-approval-permissions From eb543db84d9d036a42ffe8e12630e0ee7276455b Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 14:32:32 +0000 Subject: [PATCH 180/181] Ensuring proper branch naming conventions --- .github/workflows/pipeline-deploy-account-infrastructure.yaml | 1 + .github/workflows/pipeline-deploy-application.yaml | 1 + .github/workflows/pipeline-deploy-policies.yaml | 1 + scripts/githooks/check-branch-name.sh | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index 1c607d59..e9e5a059 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -9,6 +9,7 @@ on: branches: - "main" - "task/**" + - "develop" paths: - "infrastructure/stacks/terraform_management/**" - "infrastructure/modules/dynamodb/**" diff --git a/.github/workflows/pipeline-deploy-application.yaml b/.github/workflows/pipeline-deploy-application.yaml index f7137bbb..70f9a893 100644 --- a/.github/workflows/pipeline-deploy-application.yaml +++ b/.github/workflows/pipeline-deploy-application.yaml @@ -9,6 +9,7 @@ on: branches: - main - "task/**" + - "develop" - "dependabot/**" workflow_dispatch: # checkov:skip=CKV_GHA_7:Inputs reviewed and approved diff --git a/.github/workflows/pipeline-deploy-policies.yaml b/.github/workflows/pipeline-deploy-policies.yaml index 716f82ad..5f681dd8 100644 --- a/.github/workflows/pipeline-deploy-policies.yaml +++ b/.github/workflows/pipeline-deploy-policies.yaml @@ -9,6 +9,7 @@ on: branches: - "main" - "task/**" + - "develop" paths: - "infrastructure/stacks/github_runner/**" - "infrastructure/stacks/account_policies/**" diff --git a/scripts/githooks/check-branch-name.sh b/scripts/githooks/check-branch-name.sh index 6e1950e2..fffd16ec 100755 --- a/scripts/githooks/check-branch-name.sh +++ b/scripts/githooks/check-branch-name.sh @@ -15,7 +15,7 @@ function check_git_branch_name { function check_git_branch_name_format { BUILD_BRANCH="$1" - if [ "$BUILD_BRANCH" != 'main' ] && ! [[ $BUILD_BRANCH =~ (hotfix|task)\/(saet)-([0-9]{1,5})(_|-)([A-Za-z0-9])([A-Za-z0-9_-]{9,45})$ ]] ; then + if [ "$BUILD_BRANCH" != 'main' ] && ! [[ $BUILD_BRANCH =~ (hotfix|task)\/(saet|npt)-([0-9]{1,5})(_|-)([A-Za-z0-9])([A-Za-z0-9_-]{9,45})$ ]] ; then echo 1 fi } From cf77f17381e6b77ad1990fe6a094304fef42c153 Mon Sep 17 00:00:00 2001 From: Jack Cullen Date: Fri, 20 Feb 2026 15:28:17 +0000 Subject: [PATCH 181/181] testing signed commits --- .github/workflows/pipeline-deploy-account-infrastructure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline-deploy-account-infrastructure.yaml b/.github/workflows/pipeline-deploy-account-infrastructure.yaml index e9e5a059..d4b47d08 100644 --- a/.github/workflows/pipeline-deploy-account-infrastructure.yaml +++ b/.github/workflows/pipeline-deploy-account-infrastructure.yaml @@ -1,4 +1,4 @@ -name: Pipeline Deploy Account Level Infrastructure Pipeline +name: Pipeline Deploy Account Level Infrastructures Pipeline permissions: id-token: write