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
src/template.yaml- the app VPC and its subnet, a stand-in datacentre VPC, a transit gateway with default association and propagation enabled and an attachment on each side, at3.microserving a page on port 80 in each subnet, the Client VPN endpoint, its target network association, its connection log group, and the authorization rule for the app VPC. Two clearly-markedTODOs sit where the datacentre route and rule go.scripts/deploy.sh- generates a CA, a server certificate and a client certificate with openssl, imports the server certificate into ACM, then deploys the stack with that ARN.scripts/test.sh- reads the endpoint route table and the authorization rules and reports, per network, which half of the pair is missing.scripts/connect.sh- optional. Builds a real.ovpnprofile and brings the tunnel up, so you cancurlboth instances instead of trusting a table.scripts/teardown.sh- deletes the stack, then the certificate.solution/template.yaml- the complete build.
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
ResourceNotFoundExceptionon the certificate during deploy. The imported ARN inpki/acm-arn.txtpoints at a certificate that no longer exists, usually because teardown deleted it. Delete that file and deploy again to re-import.- The endpoint stays
pending-associate. The target network association failed. An associated subnet must have at least a /27 and twenty free addresses, and it cannot overlap the client CIDR. InvalidClientVpnEndpointAuthorizationRuleNotFoundor a route that will not create. TheDependsOn: Associationis missing. Both resources need the association to exist first, and CloudFormation will not infer that.- openvpn refuses the remote host. The exported profile’s
remoteline starts with an asterisk.connect.shreplaces it with a label; if you exported the file by hand, do the same. - The tunnel is up and
curlhangs on the app instance too. Give the instances a minute after the stack finishes; the page is served by a user-data script that runs at first boot. - The stack will not delete. The target network disassociation takes
several minutes and the transit gateway attachments a few more. Let
teardown.shfinish its wait.
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
- Reach and permission are separate controls on a Client VPN endpoint. A route says a network exists and how to get to it; an authorization rule says who may use it. Authorization defaults to deny and no rule can subtract access.
- Associating a target subnet adds the VPC’s local route to the endpoint route
table for you and moves the endpoint to
available. Nothing else is automatic: the endpoint route table learns nothing from a transit gateway. - Split tunnel pushes the endpoint route table to the client. Full tunnel
overwrites the client’s route table with
0.0.0.0/0, which means the endpoint then needs a0.0.0.0/0route and rule and a path to the internet, or the client loses everything not in your VPC. - The client CIDR must be between /22 and /12, must not overlap any network the endpoint routes to, and cannot be changed afterwards. So are the authentication options, the client certificate and the transport protocol.
- Mutual authentication cannot express groups: every rule grants all users. Group-scoped rules come from Active Directory, naming group SIDs, or from SAML, naming the group attribute the identity provider asserts. The self-service portal exists for those two and not for certificates.
Next
The rest of the Advanced Networking material is in The Exam Room.