diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 24f4801..ed31936 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,57 @@ permissions: contents: read jobs: + terraform: + name: Terraform + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: terraform + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.16.0" + terraform_wrapper: false + + - name: Terraform fmt + run: terraform fmt -check -recursive + + - name: Terraform init + run: terraform init -backend=false + + - name: Terraform validate + run: terraform validate + ci: - uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8 - with: - node-version: "24" + name: ci / ci + needs: [terraform] + if: ${{ always() && !cancelled() }} + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check jobs + env: + TERRAFORM_RESULT: ${{ needs.terraform.result }} + run: | + set -euo pipefail + fail=0 + check() { + local name="$1" + local result="$2" + case "${result}" in + success) + echo "${name}: ${result}" + ;; + *) + echo "${name}: ${result}" >&2 + fail=1 + ;; + esac + } + check terraform "${TERRAFORM_RESULT}" + exit "${fail}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 9e68815..5cae84e 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -8,10 +8,3 @@ permissions: jobs: review: uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8 - with: - # GHSA-rgw5-rvv9-x895 (brace-expansion DoS bypass of CVE-2026-14257): - # bundled transitive inside aws-cdk-lib (inBundle, not overridable via - # lockfile); adjudicated in .security-review/suppressions.json. - # aws-cdk-lib 2.263.0 bundles 5.0.8 (fixes GHSA-3jxr-9vmj-r5cp). Remove - # when aws-cdk-lib bundles >=5.0.9. - allow-ghsas: GHSA-rgw5-rvv9-x895 diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml deleted file mode 100644 index e45f107..0000000 --- a/.github/workflows/deploy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: Deploy -on: - push: - branches: [main] - -permissions: - id-token: write - contents: read - -concurrency: - group: deploy - cancel-in-progress: false - -jobs: - deploy: - uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8 - with: - node-version: "24" - secrets: - deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }} diff --git a/.gitignore b/.gitignore index 1b323b7..e9b386d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,13 @@ node_modules/ -cdk.out/ -*.js -*.d.ts -*.js.map +.terraform/ +*.tfstate +*.tfstate.* +crash.log +crash.*.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc .env diff --git a/.security-review/suppressions.json b/.security-review/suppressions.json deleted file mode 100644 index 41436b3..0000000 --- a/.security-review/suppressions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "suppressions": [ - { - "id": "npmaudit-brace-expansion", - "justification": "Bundled transitive of aws-cdk-lib (node_modules/aws-cdk-lib/node_modules/brace-expansion, inBundle: true). aws-cdk-lib 2.263.0 bundles brace-expansion 5.0.8, which clears GHSA-3jxr-9vmj-r5cp / CVE-2026-14257 but remains in range for GHSA-rgw5-rvv9-x895 / CVE-2026-69152 (fixed in >=5.0.9). npm cannot override bundled deps, so no fix is available until upstream rebundles >=5.0.9. Exposure is synth-time only: aws-cdk-lib runs during cdk synth in CI on trusted repo input, and this stack deploys an EC2 syslog host with no Node.js runtime artifacts. This entry also adjudicates the allow-ghsas exception in .github/workflows/dependency-review.yml. Remove this entry AND the allow-ghsas input on the first aws-cdk-lib bump that bundles >=5.0.9 / clears npm audit." - } - ] -} diff --git a/README.md b/README.md index 3b5fadc..d314731 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,82 @@ # syslog-server -![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) -![AWS CDK](https://img.shields.io/badge/AWS-CDK-FF9900?logo=amazonaws&logoColor=white) +![Terraform](https://img.shields.io/badge/Terraform-844FBA?logo=terraform&logoColor=white) +![AWS](https://img.shields.io/badge/AWS-FF9900?logo=amazonaws&logoColor=white) ![CI](https://github.com/Sea-Haven-Industries/syslog-server/actions/workflows/ci.yaml/badge.svg) -CDK stack for the **syslog-server** EC2 collector: receives remote syslog -(UDP/TCP 514) from the office UniFi fleet over its Elastic IP and ships it to -the `unifi-syslog` CloudWatch Logs group via the CloudWatch agent. +EC2 collector that receives remote syslog (UDP/TCP 514) from the office UniFi +fleet over an Elastic IP and ships it to the `unifi-syslog` CloudWatch Logs +group via the CloudWatch agent. -Brought under IaC for **INFRA-12** (AWS audit L-6). Previously a console/CLI -instance with no drift detection. +Deploy path (PLAT-78): HCP Terraform in seahaven-prod (`011934824531`), +workspace `syslog-server-prod`. CDK CD in mgmt is retired. ## Architecture ``` -office UniFi devices ──syslog/514──▶ EIP 184.72.154.32 ──▶ EC2 (rsyslog) - │ - /var/log/remote//*.log - │ - CloudWatch agent ──▶ unifi-syslog (90d) - │ - Syslog-NoIncomingLogs alarm ──▶ site-alerts +office UniFi devices ──syslog/514──▶ EIP (prod) ──▶ EC2 (rsyslog) + │ + /var/log/remote//*.log + │ + CloudWatch agent ──▶ unifi-syslog (90d) + │ + Syslog-NoIncomingLogs alarm ──▶ site-alerts ``` | Resource | Value | |---|---| +| Account / region | seahaven-prod `011934824531` / us-east-1 | +| HCP workspace | `syslog-server-prod` (project `seahaven-prod`; VCS `main`; working dir `terraform`; trigger `terraform/**`) | +| HCP plan/apply roles | `hcptf-syslog-server-plan` / `hcptf-syslog-server` | | Instance | `syslog-server`, t4g.nano, Amazon Linux 2023 (arm64), 30 GiB encrypted gp3 | -| Subnet | `subnet-0eea820effe1b3ae5` (public, us-east-1a, `vpc-0d3d4b67bd0cf8a68`) | -| Elastic IP | `184.72.154.32` (`eipalloc-006bdefc9802f3285`) — **unmanaged**, re-associated by ID | -| Security group | `syslog-server` — 514 tcp/udp + 22 from office IPs + VPC/VPN CIDRs; 2055/2056 udp reserved (netflow/sflow) | -| IAM role | `syslog-server-role` — `AmazonSSMManagedInstanceCore` + `CloudWatchAgentServerPolicy` | -| Log group | `unifi-syslog` (90-day retention) — created/retained by the CW agent, **not** a CFN resource (holds history; see stack comment) | -| Alarm | `Syslog-NoIncomingLogs` — `IncomingLogEvents` Sum < 1 over 2×1-day, ALARM-only → `site-alerts` | - -## CDK app - -Infrastructure is a single-stack AWS CDK app written in TypeScript. `cdk.json` is -the app manifest the CDK CLI reads on every command: its `app` entry -(`npx tsx bin/app.ts`) runs the TypeScript entry point directly through `tsx`, so -`synth`/`deploy` need no separate `tsc` compile step. The file also carries the -`watch` globs (for `cdk watch`) and the CDK feature-flag `context`. - -| Path | Role | -|---|---| -| `cdk.json` | CDK app manifest — `app` entry command, `watch` globs, feature-flag context | -| `bin/app.ts` | App entry point; instantiates `SyslogServerStack` with explicit `stackName: "syslog-server"` and env pinned to account `328440206208` / `us-east-1` | -| `lib/syslog-server-stack.ts` | The `syslog-server` stack — every resource in the table above (EC2 instance + rsyslog/CloudWatch/NetFlow user-data, security group, IAM role, EIP association, alarms) | -| `cdk.context.json` | Cached provider lookups — the VPC (`vpc-0d3d4b67bd0cf8a68`) and the pinned AL2023 AMI (`cachedInContext`); committed so synth is deterministic | - -`aws-cdk-lib` is pinned to an exact version (`2.261.0`). The npm scripts wrap the -CDK CLI — `npm run synth`, `npm run diff`, `npm run deploy` — plus -`npm run build` (`tsc` type-check). - -## Documentation - -The canonical map of Sea Haven's AWS infrastructure lives in Confluence. This project's `syslog-server` stack is represented there as a Mermaid subgraph. - -- **[AWS Architecture Map](https://seahaven.atlassian.net/wiki/spaces/IT/pages/1540098)** (Confluence, IT space, page 1540098) +| VPC | dedicated `10.40.0.0/16`, public subnet `10.40.10.0/24` | +| Elastic IP | Terraform-managed (see HCP output `public_ip`) | +| Security group | `syslog-server` — 514 tcp/udp + 22 from office IPs + VPC/VPN CIDRs; 2055/2056 udp from office | +| Instance IAM | `/tf-managed/syslog-server-role` with `syslog-server-instance-boundary`; `AmazonSSMManagedInstanceCore` + `CloudWatchAgentServerPolicy` | +| Log group | `unifi-syslog` (90-day retention) | +| Alarms | `Syslog-NoIncomingLogs`, `EC2-StatusCheck-syslog-server`, `EC2-StatusCheckSystem-syslog-server-recover` → `site-alerts` | ## Access SSM Session Manager (no key pair). SSH 22 is open from office/VPC for break-glass only. -## Deploy +## HCP first apply -CI/CD via the org reusable workflows (`ci-typescript-cdk.yaml`, -`cd-cdk.yaml`); merges to `main` deploy through the `githubdeploy-syslog-server` -OIDC role. No Docker assets, so a local `cdk deploy` is also safe. +First apply uses the hcptf-bootstrap window (exact `StringEquals` trust, never +`StringLike`): -``` -npm ci -npm run diff -npm run deploy -``` +1. Create the HCP workspace. Auto-apply off. No project-level variable set. + Working directory `terraform`. File trigger prefix `terraform/**` only. + Speculative plans on. VCS on `main`. +2. From `seahaven-org-baseline`: + `scripts/create-hcptf-bootstrap-roles.sh --account prod --allow-workspace syslog-server-prod` +3. Point workspace `TFC_AWS_APPLY_ROLE_ARN` / `TFC_AWS_PLAN_ROLE_ARN` at + `hcptf-bootstrap` / `hcptf-bootstrap-plan`. Set `TFC_AWS_PROVIDER_AUTH=true`. + Never `TFC_AWS_RUN_ROLE_ARN`. +4. One manual apply. This creates the scoped `hcptf-*` roles, the instance + boundary, VPC, instance, EIP, log group, and alarms. +5. Retarget `TFC_AWS_*` to `hcptf-syslog-server` / `hcptf-syslog-server-plan`. + Re-run the create script with no `--allow-workspace`. +6. Second manual apply as the scoped role. After live-path proof, seal + auto-apply on. -## Notes +`Syslog-NoIncomingLogs` defaults `treat_missing_data` to `notBreaching` so the +empty prod log group does not page `site-alerts` before UniFi is re-pointed. +After devices deliver to the new EIP, set `no_logs_treat_missing_data=breaching`. -- **EIP is unmanaged.** CloudFormation associates it but never releases it, so - the public forwarding target survives any instance replacement. -- **AMI is pinned in `cdk.context.json`** (`cachedInContext`). An AL2023 AMI - change forces instance replacement — refresh deliberately with - `cdk context --reset && cdk synth`. -- To widen device coverage of the forwarded syslog feed, see **INFRA-11** - (UniFi controller remote-logging config). +HCP outputs to copy: `public_ip`, `instance_id`, `hcptf_apply_role_arn`, +`hcptf_plan_role_arn`. + +AMI is pinned in `var.ami_id`. An AMI change forces instance replacement. +User-data changes also replace the instance (the box is stateless; logs live +in CloudWatch; the EIP re-associates). + +## Documentation + +The canonical map of Sea Haven's AWS infrastructure lives in Confluence. + +- **[AWS Architecture Map](https://seahaven.atlassian.net/wiki/spaces/IT/pages/1540098)** (Confluence, IT space, page 1540098) + +To widen device coverage of the forwarded syslog feed, see **INFRA-11** +(UniFi controller remote-logging config). diff --git a/bin/app.ts b/bin/app.ts deleted file mode 100644 index c5b42f8..0000000 --- a/bin/app.ts +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node -import "source-map-support/register"; -import * as cdk from "aws-cdk-lib"; -import { SyslogServerStack } from "../lib/syslog-server-stack"; - -const app = new cdk.App(); - -new SyslogServerStack(app, "syslog-server", { - stackName: "syslog-server", - env: { account: "328440206208", region: "us-east-1" }, -}); diff --git a/cdk.context.json b/cdk.context.json deleted file mode 100644 index 05f2824..0000000 --- a/cdk.context.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "vpc-provider:account=328440206208:filter.vpc-id=vpc-0d3d4b67bd0cf8a68:region=us-east-1:returnAsymmetricSubnets=true": { - "vpcId": "vpc-0d3d4b67bd0cf8a68", - "vpcCidrBlock": "10.20.0.0/16", - "ownerAccountId": "328440206208", - "availabilityZones": [], - "vpnGatewayId": "vgw-073737d44762dffc2", - "subnetGroups": [ - { - "name": "Private", - "type": "Private", - "subnets": [ - { - "subnetId": "subnet-04e38c507e96f1926", - "cidr": "10.20.30.0/24", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-06a2f56f492b9b4de" - }, - { - "subnetId": "subnet-0a0b4fc6f296dfba5", - "cidr": "10.20.40.0/24", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-01e152fe5cabca7d6" - } - ] - }, - { - "name": "Public", - "type": "Public", - "subnets": [ - { - "subnetId": "subnet-0eea820effe1b3ae5", - "cidr": "10.20.10.0/24", - "availabilityZone": "us-east-1a", - "routeTableId": "rtb-0f2232493a5c43fe8" - }, - { - "subnetId": "subnet-0012f5895182c1580", - "cidr": "10.20.20.0/24", - "availabilityZone": "us-east-1b", - "routeTableId": "rtb-0f2232493a5c43fe8" - } - ] - } - ] - }, - "ssm:account=328440206208:parameterName=/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64:region=us-east-1": "ami-02c114835b4e7739f" -} diff --git a/cdk.json b/cdk.json deleted file mode 100644 index 4dc9181..0000000 --- a/cdk.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "app": "npx tsx bin/app.ts", - "watch": { - "include": ["**"], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "node_modules", - "cdk.out" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": ["aws"] - } -} diff --git a/lib/syslog-server-stack.ts b/lib/syslog-server-stack.ts deleted file mode 100644 index b28f7a3..0000000 --- a/lib/syslog-server-stack.ts +++ /dev/null @@ -1,351 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as iam from "aws-cdk-lib/aws-iam"; -import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch"; -import * as cwactions from "aws-cdk-lib/aws-cloudwatch-actions"; -import * as sns from "aws-cdk-lib/aws-sns"; -import { Construct } from "constructs"; - -/** - * syslog-server — EC2 collector that receives remote syslog (UDP/TCP 514) from - * the office UniFi fleet over the EIP and ships it to the `unifi-syslog` - * CloudWatch Logs group via the CloudWatch agent. - * - * Brought under IaC for INFRA-12 (AWS audit L-6). Recreated to mirror the - * file-share/forgejo CDK pattern; the existing EIP (184.72.154.32) is imported - * by allocation ID and re-associated so the forwarding target is unchanged. - * - * The `unifi-syslog` log group is intentionally NOT a CloudFormation resource: - * it holds 90 days of history and is created/retained by the CloudWatch agent - * per the user-data config below (log_group_name + retention_in_days). Managing - * it as a CFN resource would either collide with the live group on create or - * risk deleting the history on a future replacement. The agent owns it; this - * stack owns the instance that runs the agent. - */ -export class SyslogServerStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - const vpc = ec2.Vpc.fromLookup(this, "SeaHavenVpc", { - vpcId: "vpc-0d3d4b67bd0cf8a68", - }); - - // Public subnet (IGW route present) — the instance must be internet-facing - // so the office gateways can forward syslog to the EIP. - const publicSubnet = ec2.Subnet.fromSubnetAttributes(this, "PublicSubnet", { - subnetId: "subnet-0eea820effe1b3ae5", - availabilityZone: "us-east-1a", - }); - - // Office public IPs that forward syslog (see reference_office_ips). - const OFFICE_1 = "47.21.61.4/32"; - const OFFICE_2 = "96.250.164.146/32"; - - const sg = new ec2.SecurityGroup(this, "SecurityGroup", { - vpc, - securityGroupName: "syslog-server", - description: "Syslog collector - rsyslog 514 from office + VPC", - allowAllOutbound: true, - }); - - // Remote syslog (UDP + TCP 514) from the office public IPs and the internal - // VPC / VPN CIDRs. - for (const proto of [ec2.Port.tcp(514), ec2.Port.udp(514)]) { - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_1), proto, "syslog from office-1"); - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_2), proto, "syslog from office-2"); - sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), proto, "syslog from office VPN"); - sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), proto, "syslog from VPC"); - sg.addIngressRule(ec2.Peer.ipv4("10.30.0.0/16"), proto, "syslog from VPN pool"); - } - - // SSH (SSM is the primary access path; 22 kept for break-glass from office/VPC). - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_1), ec2.Port.tcp(22), "SSH from office-1"); - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_2), ec2.Port.tcp(22), "SSH from office-2"); - sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), ec2.Port.tcp(22), "SSH from office VPN"); - sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), ec2.Port.tcp(22), "SSH from VPC"); - - // NetFlow / sFlow ingress reserved from the office IPs. No collector is - // configured in user-data yet; kept to preserve the prior capability. - for (const port of [ec2.Port.udp(2055), ec2.Port.udp(2056)]) { - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_1), port, "netflow/sflow from office-1"); - sg.addIngressRule(ec2.Peer.ipv4(OFFICE_2), port, "netflow/sflow from office-2"); - } - - const role = new iam.Role(this, "InstanceRole", { - roleName: "syslog-server-role", - assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), - managedPolicies: [ - iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"), - iam.ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"), - ], - }); - - const userData = ec2.UserData.forLinux(); - userData.addCommands( - "set -euxo pipefail", - "", - "# ── 1 GiB swap (build headroom + stability on the 512 MiB t4g.nano) ──", - "if [ ! -f /swapfile ]; then", - " fallocate -l 1G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=1024", - " chmod 600 /swapfile", - " mkswap /swapfile", - " echo '/swapfile none swap sw 0 0' >> /etc/fstab", - "fi", - "swapon -a || true", - "", - "# ── rsyslog: listen on UDP/TCP 514 ──", - "dnf install -y rsyslog", - "cat > /etc/rsyslog.d/10-listen.conf <<'EOF'", - 'module(load="imudp")', - 'input(type="imudp" port="514")', - 'module(load="imtcp")', - 'input(type="imtcp" port="514")', - "EOF", - "", - "# ── Write remote syslog to /var/log/remote//.log ──", - "cat > /etc/rsyslog.d/20-remote.conf <<'EOF'", - 'template(name="RemoteHost" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")', - "if $fromhost-ip != '127.0.0.1' then {", - ' action(type="omfile" dynaFile="RemoteHost" createDirs="on")', - " stop", - "}", - "EOF", - "", - "mkdir -p /var/log/remote", - "systemctl enable rsyslog", - "systemctl restart rsyslog", - "", - "# ── Rotate /var/log/remote so it can't grow unbounded ──", - "# CloudWatch (90d) is the system of record; these local files are just a", - "# spool for the CW agent, so keep only a short window. copytruncate keeps", - "# rsyslog's open dynaFile handles valid (truncate in place, same inode).", - "cat > /etc/logrotate.d/remote-syslog <<'EOF'", - "/var/log/remote/*/*.log {", - " daily", - " rotate 7", - " compress", - " delaycompress", - " missingok", - " notifempty", - " copytruncate", - "}", - "EOF", - "", - "# ── CloudWatch agent: ship /var/log/remote/**/*.log to unifi-syslog ──", - "dnf install -y amazon-cloudwatch-agent", - "cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json <<'EOF'", - "{", - ' "logs": {', - ' "logs_collected": {', - ' "files": {', - ' "collect_list": [', - " {", - ' "file_path": "/var/log/remote/**/*.log",', - ' "log_group_name": "unifi-syslog",', - ' "log_stream_name": "{hostname}/{file_name}",', - ' "retention_in_days": 90', - " }", - " ]", - " }", - " }", - " }", - "}", - "EOF", - "", - "/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \\", - " -a fetch-config -m ec2 \\", - " -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s", - "systemctl enable amazon-cloudwatch-agent", - "", - "# ── NetFlow/IPFIX collectors (nfcapd) ──", - "# nfdump is not packaged for AL2023; build 1.6.23 from source (needs", - "# rrdtool-devel for librrd). Reconstructed under IaC for INFRA-12 — the", - "# original instance ran these as hand-installed systemd units. Captures", - "# are local-only (no consumer/shipping today); 30-day retention enforced.", - "dnf install -y gcc gcc-c++ make automake autoconf libtool flex bison libpcap-devel zlib-devel bzip2-devel rrdtool-devel tar", - "NFVER=1.6.23", - "curl -sfL https://github.com/phaag/nfdump/archive/refs/tags/v${NFVER}.tar.gz | tar xz -C /tmp", - "( cd /tmp/nfdump-${NFVER} && ./autogen.sh && ./configure && make -j1 && make install )", - "ldconfig", - "", - "mkdir -p /var/log/netflow/ronkonkoma /var/log/netflow/locust", - "chown -R ec2-user:ec2-user /var/log/netflow", - "", - "# Ronkonkoma gateway -> UDP 2055", - "cat > /etc/systemd/system/nfcapd.service <<'EOF'", - "[Unit]", - "Description=nfcapd NetFlow collector (Ronkonkoma, udp/2055)", - "After=network.target", - "[Service]", - "Type=simple", - "User=ec2-user", - "ExecStart=/usr/local/bin/nfcapd -p 2055 -l /var/log/netflow/ronkonkoma", - "Restart=always", - "[Install]", - "WantedBy=multi-user.target", - "EOF", - "", - "# Locust Ave gateway -> UDP 2056", - "cat > /etc/systemd/system/nfcapd-locust.service <<'EOF'", - "[Unit]", - "Description=nfcapd NetFlow collector (Locust Ave, udp/2056)", - "After=network.target", - "[Service]", - "Type=simple", - "User=ec2-user", - "ExecStart=/usr/local/bin/nfcapd -p 2056 -l /var/log/netflow/locust", - "Restart=always", - "[Install]", - "WantedBy=multi-user.target", - "EOF", - "", - "# 30-day retention sweep (daily 03:30 UTC)", - "cat > /usr/local/sbin/netflow-retention.sh <<'EOF'", - "#!/bin/bash", - "find /var/log/netflow -type f -name 'nfcapd.*' -mtime +30 -delete", - "EOF", - "chmod +x /usr/local/sbin/netflow-retention.sh", - "cat > /etc/systemd/system/netflow-retention.service <<'EOF'", - "[Unit]", - "Description=Delete NetFlow captures older than 30 days", - "[Service]", - "Type=oneshot", - "ExecStart=/usr/local/sbin/netflow-retention.sh", - "EOF", - "cat > /etc/systemd/system/netflow-retention.timer <<'EOF'", - "[Unit]", - "Description=Daily NetFlow retention sweep", - "[Timer]", - "OnCalendar=*-*-* 03:30:00 UTC", - "Persistent=true", - "[Install]", - "WantedBy=timers.target", - "EOF", - "", - "systemctl daemon-reload", - "systemctl enable --now nfcapd.service nfcapd-locust.service netflow-retention.timer", - ); - - const instance = new ec2.Instance(this, "Instance", { - instanceName: "syslog-server", - vpc, - vpcSubnets: { subnets: [publicSubnet] }, - instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO), - machineImage: ec2.MachineImage.latestAmazonLinux2023({ - cpuType: ec2.AmazonLinuxCpuType.ARM_64, - // Cache the resolved AMI in cdk.context.json so deploys don't implicitly - // pick up new AL2023 releases (AMI change forces instance replacement). - // Refresh deliberately: cdk context --reset && cdk synth - cachedInContext: true, - }), - securityGroup: sg, - role, - userData, - // A user-data change must actually re-run, so force instance replacement - // (the box is stateless — logs live in CloudWatch, the EIP re-associates). - userDataCausesReplacement: true, - blockDevices: [ - { - deviceName: "/dev/xvda", - volume: ec2.BlockDeviceVolume.ebs(30, { - volumeType: ec2.EbsDeviceVolumeType.GP3, - encrypted: true, - }), - }, - ], - }); - - // Re-associate the existing Elastic IP (184.72.154.32) so the UniFi fleet's - // forwarding target is unchanged. The allocation is UNMANAGED (referenced by - // ID) — CloudFormation can associate it but never release it. - new ec2.CfnEIPAssociation(this, "EipAssociation", { - allocationId: "eipalloc-006bdefc9802f3285", - instanceId: instance.instanceId, - }); - - // ALARM-only "no incoming logs" alarm to the shared site-alerts topic - // (alias/seahaven-alarm-topics CMK). Mirrors the prior standalone alarm: - // IncomingLogEvents (Sum) < 1 over two 1-day periods. 2-day window tolerates - // quiet weekends; treatMissingData=breaching catches a dead pipeline. - const alarmTopic = sns.Topic.fromTopicArn( - this, "SiteAlerts", "arn:aws:sns:us-east-1:328440206208:site-alerts", - ); - - const noLogsAlarm = new cloudwatch.Alarm(this, "NoIncomingLogsAlarm", { - alarmName: "Syslog-NoIncomingLogs", - alarmDescription: - "No log events delivered to unifi-syslog for 2 days — syslog pipeline may be down.", - metric: new cloudwatch.Metric({ - namespace: "AWS/Logs", - metricName: "IncomingLogEvents", - dimensionsMap: { LogGroupName: "unifi-syslog" }, - statistic: "Sum", - period: cdk.Duration.days(1), - }), - threshold: 1, - comparisonOperator: cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD, - evaluationPeriods: 2, - treatMissingData: cloudwatch.TreatMissingData.BREACHING, - }); - noLogsAlarm.addAlarmAction(new cwactions.SnsAction(alarmTopic)); - - // Primary EC2 status-check alarm — pages on-call when the box is hung or - // unreachable. Uses the combined StatusCheckFailed metric so it covers BOTH - // instance and system failures. Dimension is instance.instanceId (Ref), not - // a literal id, so the alarm tracks the CFN-managed instance across future - // replacements (e.g. userDataCausesReplacement above) — this is the durable - // fix for the orphaned EC2-StatusCheck-syslog-server alarm that pointed at a - // since-terminated instance. Maximum>=1 over two 5-min periods; - // treatMissingData=breaching so a metric gap (instance gone/not reporting) - // also fires. - const statusCheckAlarm = new cloudwatch.Alarm(this, "StatusCheckFailedAlarm", { - alarmName: "EC2-StatusCheck-syslog-server", - alarmDescription: - "syslog-server EC2 status check failed (instance and/or system) for 10 min — host may be hung or unreachable.", - metric: new cloudwatch.Metric({ - namespace: "AWS/EC2", - metricName: "StatusCheckFailed", - dimensionsMap: { InstanceId: instance.instanceId }, - statistic: "Maximum", - period: cdk.Duration.seconds(300), - }), - threshold: 1, - comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 2, - treatMissingData: cloudwatch.TreatMissingData.BREACHING, - }); - statusCheckAlarm.addAlarmAction(new cwactions.SnsAction(alarmTopic)); - - // Auto-recovery alarm — migrates the instance onto healthy hardware on an - // underlying host failure (instance id, EIP association and EBS volume are - // preserved). AWS only permits the RECOVER action on StatusCheckFailed_System - // (NOT the combined StatusCheckFailed / _Instance), so this is a separate - // alarm. Per AWS guidance for recovery alarms, missing data is treated as - // NOT breaching to avoid a spurious recover on transient INSUFFICIENT_DATA, - // and evaluation periods differ from any reboot alarm to avoid a race. - const systemRecoverAlarm = new cloudwatch.Alarm(this, "StatusCheckSystemRecoverAlarm", { - alarmName: "EC2-StatusCheckSystem-syslog-server-recover", - alarmDescription: - "syslog-server EC2 system status check failed — underlying host impaired; auto-recovering onto new hardware.", - metric: new cloudwatch.Metric({ - namespace: "AWS/EC2", - metricName: "StatusCheckFailed_System", - dimensionsMap: { InstanceId: instance.instanceId }, - statistic: "Maximum", - period: cdk.Duration.seconds(300), - }), - threshold: 1, - comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD, - evaluationPeriods: 2, - treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING, - }); - systemRecoverAlarm.addAlarmAction(new cwactions.SnsAction(alarmTopic)); - systemRecoverAlarm.addAlarmAction(new cwactions.Ec2Action(cwactions.Ec2InstanceAction.RECOVER)); - - new cdk.CfnOutput(this, "InstanceId", { value: instance.instanceId }); - new cdk.CfnOutput(this, "PublicIp", { - value: "184.72.154.32", - description: "Elastic IP — UniFi remote-syslog forwarding target", - }); - } -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e08ad11..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1242 +0,0 @@ -{ - "name": "syslog-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "syslog-server", - "version": "1.0.0", - "dependencies": { - "aws-cdk-lib": "2.266.0", - "constructs": "^10.8.1" - }, - "bin": { - "app": "bin/app.js" - }, - "devDependencies": { - "@types/node": "^26.2.0", - "aws-cdk": "^2.1138.0", - "source-map-support": "^0.5.21", - "tsx": "4.23.12", - "typescript": "~7.0.2" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.292", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.292.tgz", - "integrity": "sha512-d4aMFsAFj19FtxVyw8IzlUKv5Zu4sIvgnEjI2IU6IWBJgVbJ4aFnadANqYa+6MwB1CQbGOg0jh8WE77M+Nb/9A==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.2.tgz", - "integrity": "sha512-pDiuqH+qY3zM9lhhLjbKJ1tnKOHzQ2V4Wr/3qsxyKeKAkuPMI/BVGvZG1PbrikUw949cGVTfVEt4ETKKYnrj0Q==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "54.14.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-54.14.0.tgz", - "integrity": "sha512-JCZCzgp3SuXQVljaKqXnttHzcezEHt9Ag/YipK0XwUFD+Iz2T4jY7gUc3pA25Uq6pzY2n9DvO/nEU++dPXW4Rw==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "^1.5.0", - "semver": "^7.8.5" - }, - "engines": { - "node": ">= 18.0.0" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.8.5", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@typescript/typescript-aix-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", - "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-darwin-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-darwin-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", - "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-freebsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", - "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-freebsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", - "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-arm": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", - "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", - "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-loong64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", - "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-mips64el": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", - "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", - "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-riscv64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", - "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-s390x": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", - "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", - "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-netbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", - "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-netbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", - "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-openbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", - "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-openbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", - "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-sunos-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", - "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-win32-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", - "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-win32-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", - "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/aws-cdk": { - "version": "2.1138.0", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1138.0.tgz", - "integrity": "sha512-gZ5F8rmh+qc7ZNWsbaXYoV+p7jSYynRRg70s7FAn3VmzRaSkTE31ijpQHYroxCbDEtKSzgN63ORR/WuZmvXAwA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 18.0.0" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.266.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.266.0.tgz", - "integrity": "sha512-sBQU42pEc9ud3yeVU2En2euQRUhCg63eaJIPEVpBtE5aPhJjN3d9MkzQ9eYGLVXP3fnohUE54BiVOdUrkEDUbg==", - "bundleDependencies": [ - "@aws/cloudformation-validate", - "@balena/dockerignore", - "@aws-cdk/cloud-assembly-api", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "2.2.292", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.2", - "@aws-cdk/cloud-assembly-api": "^2.2.6", - "@aws-cdk/cloud-assembly-schema": "^54.11.0", - "@aws/cloudformation-validate": "1.7.0-beta", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.3.6", - "ignore": "^5.3.2", - "jsonschema": "^1.5.0", - "mime-types": "^2.1.35", - "minimatch": "^10.2.5", - "punycode": "^2.3.1", - "semver": "^7.8.5", - "yaml": "1.10.3" - }, - "engines": { - "node": ">= 20.0.0" - }, - "peerDependencies": { - "constructs": "^10.5.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { - "version": "2.2.6", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "^1.5.0", - "semver": "^7.8.4" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "@aws-cdk/cloud-assembly-schema": ">=54.5.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@aws/cloudformation-validate": { - "version": "1.7.0-beta", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "4.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "5.0.9", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.3.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.2.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "10.2.5", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.8.5", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.3", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/constructs": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.8.1.tgz", - "integrity": "sha512-98yGXYyhePqPYh3cYu8nzBERmAhC0DONe3UD03okK0nehZ7hYP4wgZuf02a04+uOWxnTJ5Rpp5m0GRNpwyLGGA==", - "license": "Apache-2.0" - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/tsx": { - "version": "4.23.12", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", - "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/typescript": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc" - }, - "engines": { - "node": ">=16.20.0" - }, - "optionalDependencies": { - "@typescript/typescript-aix-ppc64": "7.0.2", - "@typescript/typescript-darwin-arm64": "7.0.2", - "@typescript/typescript-darwin-x64": "7.0.2", - "@typescript/typescript-freebsd-arm64": "7.0.2", - "@typescript/typescript-freebsd-x64": "7.0.2", - "@typescript/typescript-linux-arm": "7.0.2", - "@typescript/typescript-linux-arm64": "7.0.2", - "@typescript/typescript-linux-loong64": "7.0.2", - "@typescript/typescript-linux-mips64el": "7.0.2", - "@typescript/typescript-linux-ppc64": "7.0.2", - "@typescript/typescript-linux-riscv64": "7.0.2", - "@typescript/typescript-linux-s390x": "7.0.2", - "@typescript/typescript-linux-x64": "7.0.2", - "@typescript/typescript-netbsd-arm64": "7.0.2", - "@typescript/typescript-netbsd-x64": "7.0.2", - "@typescript/typescript-openbsd-arm64": "7.0.2", - "@typescript/typescript-openbsd-x64": "7.0.2", - "@typescript/typescript-sunos-x64": "7.0.2", - "@typescript/typescript-win32-arm64": "7.0.2", - "@typescript/typescript-win32-x64": "7.0.2" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index a37fdeb..0000000 --- a/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "syslog-server", - "version": "1.0.0", - "bin": { - "app": "bin/app.js" - }, - "scripts": { - "build": "tsc", - "cdk": "cdk", - "synth": "cdk synth", - "deploy": "cdk deploy", - "diff": "cdk diff" - }, - "devDependencies": { - "@types/node": "^26.2.0", - "aws-cdk": "^2.1138.0", - "source-map-support": "^0.5.21", - "tsx": "4.23.12", - "typescript": "~7.0.2" - }, - "dependencies": { - "aws-cdk-lib": "2.266.0", - "constructs": "^10.8.1" - } -} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..71636d1 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>Sea-Haven-Industries/.github"] +} diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..c13abd8 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,26 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "6.64.0" + constraints = "~> 6.64" + hashes = [ + "h1:wXARLY+IeQ7ufYxCLTPCwToWGMRvOpiOTfJS97iwUzI=", + "zh:07172315d67bc9781240272759cdfc7bd32b7e72384a56862c2c1da3cca99a81", + "zh:154ce7d2659de9a59ddfe96d7cab41a9ddc2cb267a7d4bcdf4e737ff2ffdec06", + "zh:17324d4335a7a7ac01cc23eded530775606680ff53b47cb74a3cb95d1121f836", + "zh:307ab92324ec5a61b124881ab8cac1d9e316f4527dfd0e1b59794c229407eb4e", + "zh:31e25f1903661332e36a95283042dd3ec50b47c186db00663fbd976a11e6a6b2", + "zh:3311d9f3bd12a24886027dbe73859dcd1e67bd0e3046227a338cf2c7ca04d18e", + "zh:37916156a3aac3b29be3acebd15d53145ea4ab5d4aaa825eaebe75481fa00500", + "zh:4158cb8c38b3ac6aa98eb15935ec6bd7c30838d85d2b00acc9812df8382ae908", + "zh:5bfb9499c66d9db5b34dc5c60f426a1ab1baa5457ce2aefebca826a9c3f92fb0", + "zh:6eb29ead5a4aca3b1f35812e7e8c75419180e1928e479b458f206861277736db", + "zh:7a82b6dd0c0cdef8045a4adfbddd36acb86b6b23fcbed8e189c2d71f7dc4a502", + "zh:9556bd792032c3f7e73ea4dd08cec88dc1327f5a4a57d79c30ba844ae2b9a3c0", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:c5234180464cb800c83a41f57462742b802c150ad7d4417626fcd9cb511c01d2", + "zh:cd776b83b1f7b36635957350afe7ce28ba4e4ea3a5e2deb00d13dbd3b35d9d40", + "zh:fb583a7b791c6f915b86573d04f05ddbf7f1a5e4120c5d8a7450a3086c1225c4", + ] +} diff --git a/terraform/alarms.tf b/terraform/alarms.tf new file mode 100644 index 0000000..3659fc8 --- /dev/null +++ b/terraform/alarms.tf @@ -0,0 +1,56 @@ +resource "aws_cloudwatch_metric_alarm" "no_incoming_logs" { + alarm_name = "Syslog-NoIncomingLogs" + alarm_description = "No log events delivered to unifi-syslog for 2 days — syslog pipeline may be down." + comparison_operator = "LessThanThreshold" + evaluation_periods = 2 + metric_name = "IncomingLogEvents" + namespace = "AWS/Logs" + period = 86400 + statistic = "Sum" + threshold = 1 + treat_missing_data = var.no_logs_treat_missing_data + alarm_actions = [local.site_alerts_arn] + + dimensions = { + LogGroupName = local.log_group_name + } +} + +resource "aws_cloudwatch_metric_alarm" "status_check" { + alarm_name = "EC2-StatusCheck-syslog-server" + alarm_description = "syslog-server EC2 status check failed (instance and/or system) for 10 min — host may be hung or unreachable." + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + metric_name = "StatusCheckFailed" + namespace = "AWS/EC2" + period = 300 + statistic = "Maximum" + threshold = 1 + treat_missing_data = "breaching" + alarm_actions = [local.site_alerts_arn] + + dimensions = { + InstanceId = aws_instance.this.id + } +} + +resource "aws_cloudwatch_metric_alarm" "system_recover" { + alarm_name = "EC2-StatusCheckSystem-syslog-server-recover" + alarm_description = "syslog-server EC2 system status check failed — underlying host impaired; auto-recovering onto new hardware." + comparison_operator = "GreaterThanOrEqualToThreshold" + evaluation_periods = 2 + metric_name = "StatusCheckFailed_System" + namespace = "AWS/EC2" + period = 300 + statistic = "Maximum" + threshold = 1 + treat_missing_data = "notBreaching" + alarm_actions = [ + local.site_alerts_arn, + "arn:aws:automate:${var.aws_region}:ec2:recover", + ] + + dimensions = { + InstanceId = aws_instance.this.id + } +} diff --git a/terraform/ec2.tf b/terraform/ec2.tf new file mode 100644 index 0000000..017c7ad --- /dev/null +++ b/terraform/ec2.tf @@ -0,0 +1,39 @@ +resource "aws_instance" "this" { + ami = var.ami_id + instance_type = "t4g.nano" + subnet_id = aws_subnet.public.id + vpc_security_group_ids = [aws_security_group.this.id] + iam_instance_profile = aws_iam_instance_profile.this.name + user_data = file("${path.module}/user_data.sh") + user_data_replace_on_change = true + + root_block_device { + volume_size = 30 + volume_type = "gp3" + encrypted = true + } + + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + } + + tags = { + Name = "syslog-server" + } +} + +resource "aws_eip" "this" { + domain = "vpc" + + tags = { + Name = "syslog-server" + } + + depends_on = [aws_internet_gateway.this] +} + +resource "aws_eip_association" "this" { + instance_id = aws_instance.this.id + allocation_id = aws_eip.this.id +} diff --git a/terraform/hcp_iam.tf b/terraform/hcp_iam.tf new file mode 100644 index 0000000..d165f89 --- /dev/null +++ b/terraform/hcp_iam.tf @@ -0,0 +1,571 @@ +# HCP plan/apply roles for syslog-server-prod (PLAT-78 / PLAT-144). +# Copy of seahaven-org-baseline/examples/hcptf-workspace-iam/hcp_iam.tf.example +# with the syslog-server EC2 service set. Create, do not import. +# +# Live seahaven-hcptf-iam-management DenySelfMutation blocks DetachRolePolicy +# and PutRolePolicy on hcptf-* (including this role). First-apply sequence: +# 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh +# --account prod --allow-workspace syslog-server-prod +# 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap / +# hcptf-bootstrap-plan (workspace vars, never a project set). +# 3. One Manual apply (create roles + scoped inline + boundary + stack). +# 4. Point TFC_AWS_* back at hcptf-syslog-server / hcptf-syslog-server-plan. +# 5. Re-run the script without --allow-workspace to pin trust back to +# iam-bootstrap-prod only. +# Later apply-role IAM edits use the same window. Do not add StringLike +# on bootstrap trust. CreatePolicy stays on hcptf-bootstrap only; boundary +# document changes after seal also need that window. + +data "aws_iam_policy_document" "hcptf_apply_trust" { + statement { + sid = "HcpApply" + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"] + } + + condition { + test = "StringEquals" + variable = "app.terraform.io:aud" + values = ["aws.workload.identity"] + } + + condition { + test = "StringEquals" + variable = "app.terraform.io:sub" + values = [ + "organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:apply", + ] + } + } +} + +data "aws_iam_policy_document" "hcptf_plan_trust" { + statement { + sid = "HcpPlan" + effect = "Allow" + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"] + } + + condition { + test = "StringEquals" + variable = "app.terraform.io:aud" + values = ["aws.workload.identity"] + } + + condition { + test = "StringEquals" + variable = "app.terraform.io:sub" + values = [ + "organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:plan", + ] + } + } +} + +data "aws_iam_policy_document" "hcptf_scoped_iam" { + statement { + sid = "DenyCreatePolicy" + effect = "Deny" + actions = [ + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:SetDefaultPolicyVersion", + ] + resources = ["*"] + } + + statement { + sid = "CreateExecRoleWithBoundary" + effect = "Allow" + actions = ["iam:CreateRole"] + resources = [ + "arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*", + ] + + condition { + test = "StringLike" + variable = "iam:PermissionsBoundary" + values = [ + "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", + "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", + "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", + ] + } + } + + statement { + sid = "MutateExecRoleWithBoundary" + effect = "Allow" + actions = [ + "iam:AttachRolePolicy", + "iam:PutRolePolicy", + "iam:PutRolePermissionsBoundary", + ] + resources = [ + "arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*", + ] + + condition { + test = "StringLike" + variable = "iam:PermissionsBoundary" + values = [ + "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", + "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", + "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", + ] + } + } + + statement { + sid = "WriteExecRoles" + effect = "Allow" + actions = [ + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:TagRole", + "iam:UntagRole", + "iam:UpdateAssumeRolePolicy", + "iam:UpdateRole", + "iam:UpdateRoleDescription", + ] + resources = [ + "arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*", + ] + } + + statement { + sid = "PassExecRolesToEc2" + effect = "Allow" + actions = ["iam:PassRole"] + resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.instance_role_name}"] + + condition { + test = "StringEquals" + variable = "iam:PassedToService" + values = ["ec2.amazonaws.com"] + } + } + + statement { + sid = "InstanceProfiles" + effect = "Allow" + actions = [ + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:DeleteInstanceProfile", + "iam:GetInstanceProfile", + "iam:ListInstanceProfileTags", + "iam:RemoveRoleFromInstanceProfile", + "iam:TagInstanceProfile", + "iam:UntagInstanceProfile", + ] + resources = [ + "arn:aws:iam::${local.account_id}:instance-profile/tf-managed/${local.instance_profile_name}", + ] + } + + statement { + sid = "IamReadOnly" + effect = "Allow" + actions = [ + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:GetInstanceProfile", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfiles", + "iam:ListInstanceProfilesForRole", + "iam:ListPolicies", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:ListRoleTags", + "iam:ListRoles", + ] + resources = ["*"] + } + + statement { + sid = "DenySelfMutation" + effect = "Deny" + actions = [ + "iam:AttachRolePolicy", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DeleteRolePermissionsBoundary", + "iam:DetachRolePolicy", + "iam:PutRolePolicy", + "iam:PutRolePermissionsBoundary", + "iam:UpdateAssumeRolePolicy", + "iam:UpdateRole", + "iam:UpdateRoleDescription", + ] + resources = [ + "arn:aws:iam::${local.account_id}:role/hcptf-*", + "arn:aws:iam::${local.account_id}:role/github-cfn-execution-role", + "arn:aws:iam::${local.account_id}:role/githubdeploy-*", + "arn:aws:iam::${local.account_id}:role/cdk-hnb659fds-*", + "arn:aws:iam::${local.account_id}:role/OrganizationAccountAccessRole", + "arn:aws:iam::${local.account_id}:role/seahaven-*", + ] + } + + statement { + sid = "DenyBoundaryTampering" + effect = "Deny" + actions = [ + "iam:DeleteRolePermissionsBoundary", + "iam:DeleteUserPermissionsBoundary", + ] + resources = [ + "arn:aws:iam::${local.account_id}:role/*", + "arn:aws:iam::${local.account_id}:user/*", + ] + } + + statement { + sid = "DenyBoundaryPolicyEdit" + effect = "Deny" + actions = [ + "iam:CreatePolicyVersion", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:SetDefaultPolicyVersion", + ] + resources = ["arn:aws:iam::${local.account_id}:policy/seahaven-*"] + } +} + +data "aws_iam_policy_document" "hcptf_apply_services" { + # checkov:skip=CKV_AWS_111: EC2 VPC/instance lifecycle and describe APIs require Resource=*. Log group, alarm, and SNS writes are ARN-prefixed. + statement { + sid = "CloudWatchLogs" + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:PutRetentionPolicy", + "logs:DeleteRetentionPolicy", + "logs:TagResource", + "logs:UntagResource", + "logs:ListTagsForResource", + "logs:AssociateKmsKey", + "logs:DisassociateKmsKey", + ] + resources = [ + "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.log_group_name}", + "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.log_group_name}:*", + ] + } + + statement { + sid = "CloudWatchLogsDescribe" + effect = "Allow" + actions = ["logs:DescribeLogGroups"] + resources = ["*"] + } + + statement { + sid = "CloudWatchAlarms" + effect = "Allow" + actions = [ + "cloudwatch:PutMetricAlarm", + "cloudwatch:DeleteAlarms", + "cloudwatch:DescribeAlarms", + "cloudwatch:TagResource", + "cloudwatch:UntagResource", + "cloudwatch:ListTagsForResource", + ] + resources = [ + "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:Syslog-*", + "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:EC2-StatusCheck*syslog*", + ] + } + + statement { + sid = "CloudWatchDescribeAlarms" + effect = "Allow" + actions = ["cloudwatch:DescribeAlarms"] + resources = ["*"] + } + + statement { + sid = "SnsPublishSiteAlerts" + effect = "Allow" + actions = [ + "sns:Publish", + "sns:GetTopicAttributes", + "sns:ListTagsForResource", + ] + resources = [local.site_alerts_arn] + } + + statement { + sid = "ManageTfManagedBoundary" + effect = "Allow" + actions = [ + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:ListPolicyVersions", + "iam:ListPolicyTags", + "iam:TagPolicy", + "iam:UntagPolicy", + ] + resources = [ + "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", + ] + } + + statement { + sid = "Ec2VpcManagement" + effect = "Allow" + actions = [ + "ec2:AllocateAddress", + "ec2:AssociateAddress", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateInternetGateway", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:DeleteInternetGateway", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAddressesAttribute", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroupRules", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "ec2:DescribePrefixLists", + "ec2:DetachInternetGateway", + "ec2:DisassociateAddress", + "ec2:DisassociateRouteTable", + "ec2:ModifySecurityGroupRules", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:ReleaseAddress", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:UpdateSecurityGroupRuleDescriptionsEgress", + "ec2:UpdateSecurityGroupRuleDescriptionsIngress", + ] + resources = ["*"] + } + + statement { + sid = "Ec2InstanceManagement" + effect = "Allow" + actions = [ + "ec2:AssociateIamInstanceProfile", + "ec2:AttachVolume", + "ec2:CreateVolume", + "ec2:DeleteVolume", + "ec2:DescribeIamInstanceProfileAssociations", + "ec2:DescribeImages", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceCreditSpecifications", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInstances", + "ec2:DescribeVolumes", + "ec2:DescribeVolumeAttribute", + "ec2:DescribeVolumeStatus", + "ec2:DetachVolume", + "ec2:DisassociateIamInstanceProfile", + "ec2:GetConsoleOutput", + "ec2:ModifyInstanceAttribute", + "ec2:ModifyVolume", + "ec2:MonitorInstances", + "ec2:RebootInstances", + "ec2:ReplaceIamInstanceProfileAssociation", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:UnmonitorInstances", + ] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "hcptf_plan_refresh" { + statement { + sid = "RefreshIamRoles" + effect = "Allow" + actions = [ + "iam:GetRole", + "iam:GetRolePolicy", + "iam:GetInstanceProfile", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfilesForRole", + "iam:ListRoleTags", + ] + resources = [ + "arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*", + "arn:aws:iam::${local.account_id}:instance-profile/tf-managed/${local.stack_prefix}*", + "arn:aws:iam::${local.account_id}:role/${local.apply_role}", + "arn:aws:iam::${local.account_id}:role/${local.plan_role}", + ] + } + + statement { + sid = "RefreshManagedPolicies" + effect = "Allow" + actions = [ + "iam:GetPolicy", + "iam:GetPolicyVersion", + ] + resources = ["*"] + } + + statement { + sid = "RefreshLogs" + effect = "Allow" + actions = [ + "logs:DescribeLogGroups", + "logs:ListTagsForResource", + ] + resources = ["*"] + } + + statement { + sid = "RefreshAlarms" + effect = "Allow" + actions = [ + "cloudwatch:DescribeAlarms", + "cloudwatch:ListTagsForResource", + ] + resources = ["*"] + } + + statement { + sid = "RefreshSns" + effect = "Allow" + actions = [ + "sns:GetTopicAttributes", + "sns:ListTagsForResource", + ] + resources = [local.site_alerts_arn] + } + + statement { + sid = "RefreshEc2" + effect = "Allow" + actions = [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAddressesAttribute", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeIamInstanceProfileAssociations", + "ec2:DescribeImages", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceCreditSpecifications", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInstances", + "ec2:DescribeInternetGateways", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroupRules", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVolumeAttribute", + "ec2:DescribeVolumeStatus", + "ec2:DescribeVolumes", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "ec2:GetConsoleOutput", + ] + resources = ["*"] + } +} + +resource "aws_iam_role" "hcptf_apply" { + name = local.apply_role + assume_role_policy = data.aws_iam_policy_document.hcptf_apply_trust.json + max_session_duration = 3600 + + tags = { + Owner = "adam@seahavenind.com" + ManagedBy = "terraform" + } +} + +resource "aws_iam_role" "hcptf_plan" { + name = local.plan_role + assume_role_policy = data.aws_iam_policy_document.hcptf_plan_trust.json + max_session_duration = 3600 + + tags = { + Owner = "adam@seahavenind.com" + ManagedBy = "terraform" + } +} + +resource "aws_iam_role_policy" "hcptf_scoped_iam" { + name = "scoped-iam-management" + role = aws_iam_role.hcptf_apply.id + policy = data.aws_iam_policy_document.hcptf_scoped_iam.json +} + +resource "aws_iam_role_policy" "hcptf_apply_services" { + # checkov:skip=CKV_AWS_111: EC2 VPC/instance lifecycle and describe APIs require Resource=*. Log group, alarm, and SNS writes are ARN-prefixed. + name = "syslog-server-services" + role = aws_iam_role.hcptf_apply.id + policy = data.aws_iam_policy_document.hcptf_apply_services.json +} + +resource "aws_iam_role_policy" "hcptf_plan_refresh" { + # checkov:skip=CKV_AWS_107: ViewOnlyAccess plus this sidecar is the org HCP plan-role pattern (PLAT-144). Sidecar Get* is named (GetRole, GetPolicy, GetInstanceProfile, GetConsoleOutput, GetTopicAttributes) and scoped to this stack. It does not add iam:CreateAccessKey, secretsmanager:GetSecretValue, or ssm:GetParameter on *. + name = "syslog-server-plan-refresh" + role = aws_iam_role.hcptf_plan.id + policy = data.aws_iam_policy_document.hcptf_plan_refresh.json +} + +resource "aws_iam_role_policy_attachment" "hcptf_plan_view_only" { + role = aws_iam_role.hcptf_plan.name + policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess" +} + +resource "aws_iam_role_policy_attachments_exclusive" "hcptf_apply" { + role_name = aws_iam_role.hcptf_apply.name + policy_arns = [] +} + +resource "aws_iam_role_policy_attachments_exclusive" "hcptf_plan" { + role_name = aws_iam_role.hcptf_plan.name + policy_arns = [ + "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess", + ] +} diff --git a/terraform/iam.tf b/terraform/iam.tf new file mode 100644 index 0000000..88fe497 --- /dev/null +++ b/terraform/iam.tf @@ -0,0 +1,174 @@ +# Instance permissions boundary. Created on the first (bootstrap) apply. +# The scoped apply role denies iam:CreatePolicy / CreatePolicyVersion, +# so later edits to this document need the hcptf-bootstrap window. + +data "aws_iam_policy_document" "instance_boundary" { + # checkov:skip=CKV_AWS_111: SSM and CloudWatch agent managed policies require Resource=* for ssmmessages, ec2messages, and describe APIs. Log writes are ARN-prefixed. + statement { + sid = "CloudWatchLogsWrite" + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:PutRetentionPolicy", + ] + resources = [ + "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.log_group_name}", + "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.log_group_name}:*", + ] + } + + statement { + sid = "CloudWatchLogsDescribe" + effect = "Allow" + actions = ["logs:DescribeLogGroups"] + resources = ["*"] + } + + statement { + sid = "CloudWatchMetrics" + effect = "Allow" + actions = [ + "cloudwatch:PutMetricData", + ] + resources = ["*"] + } + + statement { + sid = "Ec2DescribeForAgent" + effect = "Allow" + actions = [ + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:DescribeInstances", + ] + resources = ["*"] + } + + statement { + sid = "SsmAgentBuckets" + effect = "Allow" + actions = [ + "s3:GetObject", + ] + resources = [ + "arn:aws:s3:::aws-ssm-*/*", + "arn:aws:s3:::aws-windows-downloads-*/*", + "arn:aws:s3:::amazon-ssm-*/*", + "arn:aws:s3:::amazon-ssm-packages-*/*", + "arn:aws:s3:::patch-baseline-snapshot-*/*", + ] + } + + statement { + sid = "SsmManagedInstance" + effect = "Allow" + actions = [ + "ssm:DescribeAssociation", + "ssm:GetDeployablePatchSnapshotForInstance", + "ssm:GetDocument", + "ssm:DescribeDocument", + "ssm:GetManifest", + "ssm:ListAssociations", + "ssm:ListInstanceAssociations", + "ssm:PutInventory", + "ssm:PutComplianceItems", + "ssm:PutConfigurePackageResult", + "ssm:UpdateAssociationStatus", + "ssm:UpdateInstanceAssociationStatus", + "ssm:UpdateInstanceInformation", + ] + resources = ["*"] + } + + statement { + sid = "SsmAgentParameters" + effect = "Allow" + actions = [ + "ssm:GetParameter", + "ssm:GetParameters", + ] + resources = [ + "arn:aws:ssm:${var.aws_region}::parameter/aws/service/*", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter/aws/service/*", + "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter/AmazonCloudWatch-*", + ] + } + + statement { + sid = "SsmMessages" + effect = "Allow" + actions = [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + ] + resources = ["*"] + } + + statement { + sid = "Ec2Messages" + effect = "Allow" + actions = [ + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply", + ] + resources = ["*"] + } +} + +resource "aws_iam_policy" "instance_boundary" { + # checkov:skip=CKV_AWS_111: SSM and CloudWatch agent managed policies require Resource=* for ssmmessages, ec2messages, and describe APIs. Log writes are ARN-prefixed. + name = local.boundary_name + path = "/tf-managed/" + description = "Per-workload EC2 permissions boundary for syslog-server (PLAT-78)." + policy = data.aws_iam_policy_document.instance_boundary.json +} + +data "aws_iam_policy_document" "instance_assume" { + statement { + sid = "Ec2Assume" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "instance" { + name = local.instance_role_name + path = "/tf-managed/" + assume_role_policy = data.aws_iam_policy_document.instance_assume.json + permissions_boundary = aws_iam_policy.instance_boundary.arn + + tags = { + Name = local.instance_role_name + } +} + +resource "aws_iam_role_policy_attachment" "ssm" { + role = aws_iam_role.instance.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_role_policy_attachment" "cloudwatch_agent" { + role = aws_iam_role.instance.name + policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy" +} + +resource "aws_iam_instance_profile" "this" { + name = local.instance_profile_name + path = "/tf-managed/" + role = aws_iam_role.instance.name +} diff --git a/terraform/locals.tf b/terraform/locals.tf new file mode 100644 index 0000000..adeeca6 --- /dev/null +++ b/terraform/locals.tf @@ -0,0 +1,33 @@ +locals { + project = "syslog-server" + account_id = "011934824531" + environment = "prod" + + hcp_project = "seahaven-prod" + hcp_workspace = "syslog-server-prod" + apply_role = "hcptf-syslog-server" + plan_role = "hcptf-syslog-server-plan" + stack_name = local.project + stack_prefix = "syslog-server-" + + instance_role_name = "syslog-server-role" + instance_profile_name = "syslog-server-profile" + boundary_name = "syslog-server-instance-boundary" + log_group_name = "unifi-syslog" + site_alerts_arn = "arn:aws:sns:${var.aws_region}:${local.account_id}:site-alerts" + + vpc_cidr = "10.40.0.0/16" + public_subnet_cidr = "10.40.10.0/24" + office_cidrs = ["47.21.61.4/32", "96.250.164.146/32"] + office_vpn_cidr = "10.10.0.0/16" + vpn_pool_cidr = "10.30.0.0/16" + syslog_vpc_cidr = local.vpc_cidr + syslog_ingress_cidrs = concat( + local.office_cidrs, + [local.office_vpn_cidr, local.vpn_pool_cidr, local.syslog_vpc_cidr], + ) + ssh_ingress_cidrs = concat( + local.office_cidrs, + [local.office_vpn_cidr, local.syslog_vpc_cidr], + ) +} diff --git a/terraform/logs.tf b/terraform/logs.tf new file mode 100644 index 0000000..c6d2bed --- /dev/null +++ b/terraform/logs.tf @@ -0,0 +1,4 @@ +resource "aws_cloudwatch_log_group" "unifi_syslog" { + name = local.log_group_name + retention_in_days = 90 +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..292fe3e --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,29 @@ +output "instance_id" { + description = "syslog-server EC2 instance id." + value = aws_instance.this.id +} + +output "public_ip" { + description = "Elastic IP — UniFi remote-syslog forwarding target." + value = aws_eip.this.public_ip +} + +output "allocation_id" { + description = "Elastic IP allocation id." + value = aws_eip.this.allocation_id +} + +output "log_group_name" { + description = "CloudWatch Logs group the agent ships remote syslog into." + value = aws_cloudwatch_log_group.unifi_syslog.name +} + +output "hcptf_apply_role_arn" { + description = "HCP apply role ARN. Set TFC_AWS_APPLY_ROLE_ARN after the bootstrap window." + value = aws_iam_role.hcptf_apply.arn +} + +output "hcptf_plan_role_arn" { + description = "HCP plan role ARN. Set TFC_AWS_PLAN_ROLE_ARN after the bootstrap window." + value = aws_iam_role.hcptf_plan.arn +} diff --git a/terraform/providers.tf b/terraform/providers.tf new file mode 100644 index 0000000..c3854cb --- /dev/null +++ b/terraform/providers.tf @@ -0,0 +1,12 @@ +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = local.project + Environment = "prod" + ManagedBy = "terraform" + Workspace = local.hcp_workspace + } + } +} diff --git a/terraform/user_data.sh b/terraform/user_data.sh new file mode 100644 index 0000000..da1d592 --- /dev/null +++ b/terraform/user_data.sh @@ -0,0 +1,143 @@ +#!/bin/bash +set -euxo pipefail + +# ── 1 GiB swap (build headroom + stability on the 512 MiB t4g.nano) ── +if [ ! -f /swapfile ]; then + fallocate -l 1G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=1024 + chmod 600 /swapfile + mkswap /swapfile + echo '/swapfile none swap sw 0 0' >> /etc/fstab +fi +swapon -a || true + +# ── rsyslog: listen on UDP/TCP 514 ── +dnf install -y rsyslog +cat > /etc/rsyslog.d/10-listen.conf <<'EOF' +module(load="imudp") +input(type="imudp" port="514") +module(load="imtcp") +input(type="imtcp" port="514") +EOF + +# ── Write remote syslog to /var/log/remote//.log ── +cat > /etc/rsyslog.d/20-remote.conf <<'EOF' +template(name="RemoteHost" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log") +if $fromhost-ip != '127.0.0.1' then { + action(type="omfile" dynaFile="RemoteHost" createDirs="on") + stop +} +EOF + +mkdir -p /var/log/remote +systemctl enable rsyslog +systemctl restart rsyslog + +# ── Rotate /var/log/remote so it can't grow unbounded ── +# CloudWatch (90d) is the system of record; these local files are just a +# spool for the CW agent, so keep only a short window. copytruncate keeps +# rsyslog's open dynaFile handles valid (truncate in place, same inode). +cat > /etc/logrotate.d/remote-syslog <<'EOF' +/var/log/remote/*/*.log { + daily + rotate 7 + compress + delaycompress + missingok + notifempty + copytruncate +} +EOF + +# ── CloudWatch agent: ship /var/log/remote/**/*.log to unifi-syslog ── +dnf install -y amazon-cloudwatch-agent +cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json <<'EOF' +{ + "logs": { + "logs_collected": { + "files": { + "collect_list": [ + { + "file_path": "/var/log/remote/**/*.log", + "log_group_name": "unifi-syslog", + "log_stream_name": "{hostname}/{file_name}", + "retention_in_days": 90 + } + ] + } + } + } +} +EOF + +/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \ + -a fetch-config -m ec2 \ + -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s +systemctl enable amazon-cloudwatch-agent + +# ── NetFlow/IPFIX collectors (nfcapd) ── +# nfdump is not packaged for AL2023; build 1.6.23 from source (needs +# rrdtool-devel for librrd). Reconstructed under IaC for INFRA-12 — the +# original instance ran these as hand-installed systemd units. Captures +# are local-only (no consumer/shipping today); 30-day retention enforced. +dnf install -y gcc gcc-c++ make automake autoconf libtool flex bison libpcap-devel zlib-devel bzip2-devel rrdtool-devel tar +NFVER=1.6.23 +curl -sfL https://github.com/phaag/nfdump/archive/refs/tags/v${NFVER}.tar.gz | tar xz -C /tmp +( cd /tmp/nfdump-${NFVER} && ./autogen.sh && ./configure && make -j1 && make install ) +ldconfig + +mkdir -p /var/log/netflow/ronkonkoma /var/log/netflow/locust +chown -R ec2-user:ec2-user /var/log/netflow + +# Ronkonkoma gateway -> UDP 2055 +cat > /etc/systemd/system/nfcapd.service <<'EOF' +[Unit] +Description=nfcapd NetFlow collector (Ronkonkoma, udp/2055) +After=network.target +[Service] +Type=simple +User=ec2-user +ExecStart=/usr/local/bin/nfcapd -p 2055 -l /var/log/netflow/ronkonkoma +Restart=always +[Install] +WantedBy=multi-user.target +EOF + +# Locust Ave gateway -> UDP 2056 +cat > /etc/systemd/system/nfcapd-locust.service <<'EOF' +[Unit] +Description=nfcapd NetFlow collector (Locust Ave, udp/2056) +After=network.target +[Service] +Type=simple +User=ec2-user +ExecStart=/usr/local/bin/nfcapd -p 2056 -l /var/log/netflow/locust +Restart=always +[Install] +WantedBy=multi-user.target +EOF + +# 30-day retention sweep (daily 03:30 UTC) +cat > /usr/local/sbin/netflow-retention.sh <<'EOF' +#!/bin/bash +find /var/log/netflow -type f -name 'nfcapd.*' -mtime +30 -delete +EOF +chmod +x /usr/local/sbin/netflow-retention.sh +cat > /etc/systemd/system/netflow-retention.service <<'EOF' +[Unit] +Description=Delete NetFlow captures older than 30 days +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/netflow-retention.sh +EOF +cat > /etc/systemd/system/netflow-retention.timer <<'EOF' +[Unit] +Description=Daily NetFlow retention sweep +[Timer] +OnCalendar=*-*-* 03:30:00 UTC +Persistent=true +[Install] +WantedBy=timers.target +EOF + +systemctl daemon-reload +systemctl enable --now nfcapd.service nfcapd-locust.service netflow-retention.timer diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..aa0bcb4 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,22 @@ +variable "aws_region" { + description = "Region every resource in this configuration is created in." + type = string + default = "us-east-1" +} + +variable "ami_id" { + description = "Pinned Amazon Linux 2023 arm64 AMI. Changing this replaces the instance." + type = string + default = "ami-02c114835b4e7739f" +} + +variable "no_logs_treat_missing_data" { + description = "CloudWatch treat_missing_data for Syslog-NoIncomingLogs. Keep notBreaching until UniFi points at the new EIP, then set breaching." + type = string + default = "notBreaching" + + validation { + condition = contains(["breaching", "notBreaching", "ignore", "missing"], var.no_logs_treat_missing_data) + error_message = "no_logs_treat_missing_data must be breaching, notBreaching, ignore, or missing." + } +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..1589c1c --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.14.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.64" + } + } + + cloud { + organization = "seahaven" + + workspaces { + name = "syslog-server-prod" + } + } +} diff --git a/terraform/vpc.tf b/terraform/vpc.tf new file mode 100644 index 0000000..4471da1 --- /dev/null +++ b/terraform/vpc.tf @@ -0,0 +1,123 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_vpc" "this" { + cidr_block = local.vpc_cidr + enable_dns_support = true + enable_dns_hostnames = true + + tags = { + Name = "syslog-server-vpc" + } +} + +resource "aws_internet_gateway" "this" { + vpc_id = aws_vpc.this.id + + tags = { + Name = "syslog-server-igw" + } +} + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.this.id + cidr_block = local.public_subnet_cidr + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "syslog-server-public" + } +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.this.id + + tags = { + Name = "syslog-server-public" + } +} + +resource "aws_route" "public_default" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.this.id +} + +resource "aws_route_table_association" "public" { + subnet_id = aws_subnet.public.id + route_table_id = aws_route_table.public.id +} + +resource "aws_security_group" "this" { + name = "syslog-server" + description = "Syslog collector - rsyslog 514 from office + VPC" + vpc_id = aws_vpc.this.id + + tags = { + Name = "syslog-server" + } +} + +resource "aws_vpc_security_group_egress_rule" "all" { + security_group_id = aws_security_group.this.id + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + description = "All outbound for package installs and CloudWatch" +} + +resource "aws_vpc_security_group_ingress_rule" "syslog_tcp" { + for_each = toset(local.syslog_ingress_cidrs) + + security_group_id = aws_security_group.this.id + ip_protocol = "tcp" + from_port = 514 + to_port = 514 + cidr_ipv4 = each.value + description = "syslog TCP 514" +} + +resource "aws_vpc_security_group_ingress_rule" "syslog_udp" { + for_each = toset(local.syslog_ingress_cidrs) + + security_group_id = aws_security_group.this.id + ip_protocol = "udp" + from_port = 514 + to_port = 514 + cidr_ipv4 = each.value + description = "syslog UDP 514" +} + +resource "aws_vpc_security_group_ingress_rule" "ssh" { + for_each = toset(local.ssh_ingress_cidrs) + + security_group_id = aws_security_group.this.id + ip_protocol = "tcp" + from_port = 22 + to_port = 22 + cidr_ipv4 = each.value + description = "SSH break-glass" +} + +resource "aws_vpc_security_group_ingress_rule" "netflow_2055" { + for_each = toset(local.office_cidrs) + + security_group_id = aws_security_group.this.id + ip_protocol = "udp" + from_port = 2055 + to_port = 2055 + cidr_ipv4 = each.value + description = "netflow/sflow UDP 2055" +} + +resource "aws_vpc_security_group_ingress_rule" "netflow_2056" { + for_each = toset(local.office_cidrs) + + security_group_id = aws_security_group.this.id + ip_protocol = "udp" + from_port = 2056 + to_port = 2056 + cidr_ipv4 = each.value + description = "netflow/sflow UDP 2056" +} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 2b2e2de..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "lib": ["ES2022"], - "types": ["node"], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "inlineSourceMap": true, - "inlineSources": true, - "strictPropertyInitialization": false, - "outDir": "./cdk.out", - "rootDir": ".", - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "esModuleInterop": true - }, - "exclude": ["node_modules", "cdk.out"] -}