ANS Lab 03 - Hand a contractor a VPN that reaches both networks

Scaffold: 3/5. Two VPCs on a transit gateway, an instance in each, a PKI generated for you, and a Client VPN endpoint with mutual certificate authentication associated with the app subnet are all built. Two gaps are left, and they are a pair: the route to the datacentre range and the authorization rule for it. You add one, watch it fail; add the other instead, watch it fail differently; then add both.

The scenario

A contractor starts on Monday. They need one application VPC, 10.30.0.0/16, and one subnet in the datacentre, 10.40.7.0/24, reached across the transit gateway. Nothing else. They work from their own laptop on their own broadband, and nobody wants their household traffic arriving inside the VPC.

AWS Client VPN is the managed remote-access service for exactly this. It is an OpenVPN-based endpoint, associated with one or more subnets, that authenticates clients and then forwards their traffic into your network according to two separate controls that people routinely treat as one.

The boundary is worth fixing before you build. A whole site with a fixed public address at the far end belongs on Site-to-Site VPN. A browser-only shell or port-forward onto an instance belongs on Systems Manager Session Manager, with no client software and no address pool. Client VPN is for roaming people who need to be on the network rather than on one host.

What’s provided

Deploying src/template.yaml as shipped succeeds, and a client can connect. They reach 10.30.0.0/16 and nothing else.

Costs

A target network association is charged by the hour, around ten cents, from the moment it exists until you delete it, whether anyone is connected or not. Connections are charged separately, around five cents per connected client per hour. On top of that, two transit gateway attachments and two t3.micro instances are metered by the hour, and the endpoint’s public addresses carry the standard IPv4 address charge. This is a lab to finish in a sitting and tear down, not to leave running overnight. deploy.sh tags the stack for the lab reaper, which deletes it after 24 hours if you forget.

Step 1: deploy, and look at what the association gave you

# Defaults: stack ans-lab-03, region ap-southeast-2.
./scripts/deploy.sh
./scripts/test.sh

The endpoint takes a few minutes and the transit gateway attachments a few more. When test.sh runs, read the route table before the verdict:

|  destination     |  origin     |  status  |
|  10.30.0.0/16    |  associate  |  active  |

You did not write that route. Associating a subnet with the endpoint adds the local CIDR of the VPC the subnet lives in, automatically, with an origin of associate. It is also what moved the endpoint from pending-associate to available, and what started the association meter.

Nothing else appears, and nothing else will. The endpoint route table learns nothing from the transit gateway. Every destination past the associated VPC is a route you write and maintain by hand.

Step 2: add the route, and only the route

Open src/template.yaml, fill in the first TODO, leave the second alone, then redeploy and test:

DcRoute:
  Type: AWS::EC2::ClientVpnRoute
  DependsOn: Association
  Properties:
    ClientVpnEndpointId: !Ref Endpoint
    DestinationCidrBlock: 10.40.0.0/16
    TargetVpcSubnetId: !Ref AppSubnet
    Description: Datacentre range, via the transit gateway

TargetVpcSubnetId names the associated subnet the traffic leaves through; from there the app subnet’s own route table carries it to the transit gateway. Do not drop the DependsOn: a route cannot be created before the target network association it points at exists.

test.sh now reports a route with no rule. Authorization defaults to deny and there is no way to write a rule that subtracts access, so a destination with a route and no rule gives you a tunnel that comes up, a client route table that looks correct, and traffic that dies at the endpoint.

Step 3: swap it for the rule, and only the rule

Comment out the route you just added, and fill in the second TODO instead:

DcAuthRule:
  Type: AWS::EC2::ClientVpnAuthorizationRule
  DependsOn: Association
  Properties:
    ClientVpnEndpointId: !Ref Endpoint
    TargetNetworkCidr: 10.40.0.0/16
    AuthorizeAllGroups: true
    Description: Contractors reach the datacentre subnet

Redeploy and test. This failure is the other shape. Split tunnel pushes the endpoint route table to the client and nothing else, so with no route for 10.40.0.0/16 the client is never told the network exists. The packets go out of the home broadband instead and die out there. The permission is real, and nothing ever uses it.

Both failures look the same from the laptop: an application that will not load. They have different fixes, and the way to tell them apart is to look at the client’s own route table while connected.

Step 4: both, and then prove it

Put the route back so both resources are present, redeploy, and test.sh passes. Then stop trusting the table:

./scripts/connect.sh --up      # needs openvpn installed; runs under sudo

In another terminal, with the tunnel up:

curl http://<AppInstanceIp>/    # app tier, 10.30.0.0/16
curl http://<DcInstanceIp>/     # datacentre subnet, 10.40.7.0/24

Check your own routing table too (ip route on Linux, netstat -rn on macOS). You should see the two lab prefixes and no default route. That is the endpoint route table arriving on your machine, which is all split tunnel does.

Worth doing while you are connected: look at the security groups on the two instances. They admit 10.30.0.0/16, not the client CIDR. A subnet-associated endpoint translates the client source address to its own network interface in the associated subnet, so nothing downstream ever sees 10.99.0.0/22. That is also why the datacentre VPC’s return route is for 10.30.0.0/16 and the client pool is nowhere in it.

If it fails

Reveal the solution

Deploy the complete reference build without editing anything:

SRC=solution ./scripts/deploy.sh && ./scripts/test.sh

Tear it down

./scripts/teardown.sh

The stack goes first and the ACM certificate second, because ACM refuses to delete a certificate that is still in use and the endpoint holds it until the stack is gone.

What you just learned

Next

The rest of the Advanced Networking material is in The Exam Room.