|
| 1 | +"""Configure the I-ALiRT VPN connections to NOAA N-Wave.""" |
| 2 | + |
| 3 | +from aws_cdk import aws_ec2 as ec2 |
| 4 | +from constructs import Construct |
| 5 | + |
| 6 | + |
| 7 | +class IalirtVpnConstruct(Construct): |
| 8 | + """NOAA N-Wave customer gateways and VPN connections for I-ALiRT.""" |
| 9 | + |
| 10 | + def __init__( |
| 11 | + self, |
| 12 | + scope: Construct, |
| 13 | + construct_id: str, |
| 14 | + transit_gateway_id: str, |
| 15 | + psk: str, |
| 16 | + wash_ip: str, |
| 17 | + denv_ip: str, |
| 18 | + **kwargs, |
| 19 | + ) -> None: |
| 20 | + """Create NOAA N-Wave customer gateways and VPN connections. |
| 21 | +
|
| 22 | + Parameters |
| 23 | + ---------- |
| 24 | + scope : Construct |
| 25 | + Parent construct. |
| 26 | + construct_id : str |
| 27 | + A unique string identifier for this construct. |
| 28 | + transit_gateway_id : str |
| 29 | + The Transit Gateway to attach the VPN connections to. A Transit |
| 30 | + Gateway is used (rather than a Virtual Private Gateway) because a |
| 31 | + VGW cannot route decrypted VPN traffic to a NAT Gateway, and a NAT |
| 32 | + Gateway is required so NOAA's traffic reaches I-ALiRT via its |
| 33 | + stable Elastic IP instead of an EC2 private IP that changes |
| 34 | + whenever the Auto Scaling Group replaces the instance. |
| 35 | + psk : str |
| 36 | + Pre-shared key for IKE authentication. Pass a CDK token from |
| 37 | + ``secret_value_from_json(...).unsafe_unwrap()`` so the value is |
| 38 | + resolved by CloudFormation at deploy time and never appears in |
| 39 | + the template. |
| 40 | + wash_ip : str |
| 41 | + NOAA border router public IP at McLean, VA (WASH), retrieved from SSM. |
| 42 | + denv_ip : str |
| 43 | + NOAA border router public IP at Denver, CO (DENV), retrieved from SSM. |
| 44 | + kwargs : dict |
| 45 | + Keyword arguments. |
| 46 | + """ |
| 47 | + super().__init__(scope, construct_id, **kwargs) |
| 48 | + |
| 49 | + # Define the crypto settings for the IPSec tunnel, as specified |
| 50 | + # in the N-Wave ICD (NOAA0550). |
| 51 | + # |
| 52 | + # Phase 1 (IKE) — the handshake phase where both sides authenticate each other |
| 53 | + # and agree on encryption keys. Uses pre-shared key (PSK) |
| 54 | + # resolved at deploy time. |
| 55 | + # - IKEv2 only (NOAA requirement) |
| 56 | + # - AES-256 encryption |
| 57 | + # - SHA2-256 integrity |
| 58 | + # - DH group 14 for key exchange |
| 59 | + # - 28800s (8 hour) lifetime |
| 60 | + # |
| 61 | + # Phase 2 (ESP) — the data phase where actual traffic is encrypted. |
| 62 | + # - AES-128 or AES-256 encryption |
| 63 | + # - HMAC-SHA2-256-128 integrity |
| 64 | + # - DH group 14 (PFS — Perfect Forward Secrecy) |
| 65 | + # - 3600s (1 hour) lifetime |
| 66 | + tunnel = ec2.CfnVPNConnection.VpnTunnelOptionsSpecificationProperty( |
| 67 | + pre_shared_key=psk, |
| 68 | + ike_versions=[ |
| 69 | + ec2.CfnVPNConnection.IKEVersionsRequestListValueProperty(value="ikev2") |
| 70 | + ], |
| 71 | + phase1_encryption_algorithms=[ |
| 72 | + ec2.CfnVPNConnection.Phase1EncryptionAlgorithmsRequestListValueProperty( |
| 73 | + value="AES256" |
| 74 | + ) |
| 75 | + ], |
| 76 | + phase1_integrity_algorithms=[ |
| 77 | + ec2.CfnVPNConnection.Phase1IntegrityAlgorithmsRequestListValueProperty( |
| 78 | + value="SHA2-256" |
| 79 | + ) |
| 80 | + ], |
| 81 | + phase1_dh_group_numbers=[ |
| 82 | + ec2.CfnVPNConnection.Phase1DHGroupNumbersRequestListValueProperty( |
| 83 | + value=14 |
| 84 | + ) |
| 85 | + ], |
| 86 | + phase1_lifetime_seconds=28800, |
| 87 | + phase2_encryption_algorithms=[ |
| 88 | + ec2.CfnVPNConnection.Phase2EncryptionAlgorithmsRequestListValueProperty( |
| 89 | + value="AES128" |
| 90 | + ), |
| 91 | + ec2.CfnVPNConnection.Phase2EncryptionAlgorithmsRequestListValueProperty( |
| 92 | + value="AES256" |
| 93 | + ), |
| 94 | + ], |
| 95 | + phase2_integrity_algorithms=[ |
| 96 | + ec2.CfnVPNConnection.Phase2IntegrityAlgorithmsRequestListValueProperty( |
| 97 | + value="SHA2-256" |
| 98 | + ) |
| 99 | + ], |
| 100 | + phase2_dh_group_numbers=[ |
| 101 | + ec2.CfnVPNConnection.Phase2DHGroupNumbersRequestListValueProperty( |
| 102 | + value=14 |
| 103 | + ) |
| 104 | + ], |
| 105 | + phase2_lifetime_seconds=3600, |
| 106 | + ) |
| 107 | + |
| 108 | + # Customer Gateway - AWS's record of NOAA's router so that AWS can recognize |
| 109 | + # and accept the incoming encrypted packets. |
| 110 | + |
| 111 | + # Every AWS Site-to-Site VPN connection automatically provisions |
| 112 | + # two auto-assigned tunnel IPs. |
| 113 | + # These LASP IKE Gateways must be given to NOAA. |
| 114 | + self.vpn_connections: dict[str, ec2.CfnVPNConnection] = {} |
| 115 | + for site, ip in {"WASH": wash_ip, "DENV": denv_ip}.items(): |
| 116 | + # AWS needs to know the router's public IP and ASN to establish the tunnel. |
| 117 | + # bgp_asn=64583 is NOAA's ASN per the ICD. This must match the ASN |
| 118 | + # configured on NOAA's actual router — AWS silently rejects the BGP |
| 119 | + # session (not the tunnel itself) if the peer AS doesn't match what's |
| 120 | + # registered here. |
| 121 | + cgw = ec2.CfnCustomerGateway( |
| 122 | + self, |
| 123 | + f"NoaaCustomerGateway{site}", |
| 124 | + bgp_asn=64583, |
| 125 | + ip_address=ip, |
| 126 | + type="ipsec.1", |
| 127 | + ) |
| 128 | + |
| 129 | + # Create the VPN connection between our Transit Gateway (TGW) and |
| 130 | + # NOAA's customer gateway. Each connection gets two tunnels by |
| 131 | + # default (AWS requirement for redundancy) — both use the same |
| 132 | + # crypto settings. BGP is used (static_routes_only=False) so that |
| 133 | + # if one site (WASH or DENV) goes down, BGP automatically reroutes |
| 134 | + # traffic through the other. Data flows one way: NOAA sends to us. |
| 135 | + # We do not send to NOAA. |
| 136 | + self.vpn_connections[site] = ec2.CfnVPNConnection( |
| 137 | + self, |
| 138 | + f"NoaaVpnConnection{site}", |
| 139 | + customer_gateway_id=cgw.ref, |
| 140 | + transit_gateway_id=transit_gateway_id, |
| 141 | + type="ipsec.1", |
| 142 | + static_routes_only=False, |
| 143 | + vpn_tunnel_options_specifications=[tunnel, tunnel], |
| 144 | + ) |
0 commit comments