ЁЯПл The SchoolтА║ЁЯЪк NATтА║ЁЯУМ рдзрдбрд╛ 05 тАФ Elastic IPs рдЖрдгрд┐ allow-listing
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУМ рдзрдбрд╛ 05 тАФ Elastic IPs рдЖрдгрд┐ allow-listing

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 8 рдкреИрдХреА рдзрдбрд╛ 05 ┬╖ рдорд╛рдЧреЗ: lesson-04-per-az-routing ┬╖ рдкреБрдвреЗ: lesson-06-costs


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ04, рдЖрдгрд┐ рдлрд╛рдЯрдХрд╛рдЪрд╛ рд╕реНрдерд┐рд░ рдкрд░рддреАрдЪрд╛ рдкрддреНрддрд╛: Elastic IP. Partners рддреЛ allow-list рдХрд╛ рдХрд░рддрд╛рдд, рджреБрд╕рд░рд╛ IP рдлрд╛рдЯрдХрд╛рд▓рд╛ ports рдЪрд╛ рджреБрд╕рд░рд╛ рд╕рдВрдЪ рдХрд╕рд╛ рджреЗрддреЛ, рдЖрдгрд┐ рдкрддреНрддрд╛ рдореНрд╣рдгрдЬреЗ рдУрд│рдЦ рдХрд╛ рдирд╛рд╣реА. nat/demo.py рдордзреАрд▓ eip() рд╣реЗ рджрд╛рдЦрд╡рддреЗ.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдлрд╛рдЯрдХрд╛рддреВрди рдмрд╛рд╣реЗрд░ рдкрдбрдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрд░рд╛рдЪреНрдпрд╛ рдорд╛рдЧреЗ рд╢рд╛рд│реЗрдЪрд╛ рдкрддреНрддрд╛ рдЕрд╕рддреЛ. рдЧрд╛рд╡рд╛рддрд▓реА рдПрдХ рдмрдБрдХ рдореНрд╣рдгрддреЗ: "рдЖрдореНрд╣реА рдлрдХреНрдд рддреНрдпрд╛ рдкрддреНрддреНрдпрд╛рд╡рд░реВрди рдЖрд▓реЗрд▓реАрдЪ рдкрддреНрд░реЗ рдЙрдШрдбрддреЛ". рдЫрд╛рди тАФ рджреБрд╕рд▒реНрдпрд╛ рдЧрд╛рд╡рд╛рддрд▓рд╛ рдЕрдиреЛрд│рдЦреА рдорд╛рдгреВрд╕ рд╢рд╛рд│рд╛ рдЕрд╕рд▓реНрдпрд╛рдЪреЗ рд╕реЛрдВрдЧ рдШреЗрдК рд╢рдХрдд рдирд╛рд╣реА.

рдкрдг рдиреАрдЯ рдкрд╛рд╣рд╛. рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреНрдпрд╛ рдкрддреНрд░рд╛рдорд╛рдЧреЗ рддреЛрдЪ рдкрддреНрддрд╛ рдЕрд╕рддреЛ: рдореБрдЦреНрдпрд╛рдзреНрдпрд╛рдкрд┐рдХреЗрдЪреНрдпрд╛, рдХреЕрдиреНрдЯреАрдирдЪреНрдпрд╛, рдкрд╣рд┐рд▓реНрдпрд╛ рджрд┐рд╡рд╢реАрдЪреНрдпрд╛ рдирд╡реНрдпрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреНрдпрд╛рд╣реА. рдмрдБрдХреЗрдиреЗ рдореБрдЦреНрдпрд╛рдзреНрдпрд╛рдкрд┐рдХреЗрд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рд▓реЗрд▓рд╛ рдирд╛рд╣реА. рддрд┐рдиреЗ рд╕рдВрдкреВрд░реНрдг рд╢рд╛рд│реЗрд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рд▓рд╛ рдЖрд╣реЗ.

рдЖрдгрд┐ рд╢рд╛рд│реЗрдд рдЧрд░реНрджреА рд╡рд╛рдврд▓реА рдХреА рддреА рдлрд╛рдЯрдХрд╛рдЪрд╛ рджреБрд╕рд░рд╛ рдкрддреНрддрд╛ рдЬреЛрдбрддреЗ. рдЖрддрд╛ рдмрдБрдХреЗрд▓рд╛ рджреЛрдиреНрд╣реА рдорд╛рд╣реАрдд рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗрдд, рдирд╛рд╣реАрддрд░ рдЕрд░реНрдзреА рдкрддреНрд░реЗ рди рд╡рд╛рдЪрддрд╛рдЪ рд░рд╛рд╣рддрд╛рдд.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    app["ЁЯкС payments app"] --> gate
    web["ЁЯкС web app"] --> gate
    job["ЁЯкС batch job"] --> gate
    gate["ЁЯЪк NAT gateway<br/>ЁЯУМ 13.232.10.20<br/>ЁЯУМ 13.232.10.30"]
    gate -->|"1 from a listed IP"| partner["ЁЯПж partner firewall<br/>allow 13.232.10.20/32<br/>allow 13.232.10.30/32"]
    partner -->|"2 still check who: mTLS / signed request"| ok["тЬЕ"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/nat/lesson-diagrams.html#l05

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг partner firewalls (banks, payment providers, рд╕рд░рдХрд╛рд░реА APIs) рдмрд░реЗрдЪрджрд╛ рдлрдХреНрдд рдУрд│рдЦреАрдЪреНрдпрд╛ addresses рд╡рд░реВрдирдЪ traffic рд╕реНрд╡реАрдХрд╛рд░рддрд╛рдд. NAT рдорд╛рдЧрдЪреЗ servers рд╣реЗ рд╕реЛрдбрд╡рддрд╛рдд: рдЕрдиреЗрдХ рдмрджрд▓рдгрд╛рд░реЗ servers, рдПрдХ рдХрд┐рдВрд╡рд╛ рдХрд╛рд╣реА рд╕реНрдерд┐рд░ addresses. рдкрдг teams рдмрд░реЗрдЪрджрд╛ рдЗрдереЗрдЪ рдерд╛рдВрдмрддрд╛рдд, рдЖрдгрд┐ рдордЧ allow-list рд╣реЗрдЪ рдПрдХрдореЗрд╡ рдХреБрд▓реВрдк рдЙрд░рддреЗ тАФ рдЖрдгрд┐ VPC рдордзреАрд▓ рдХреЛрдгрддреЗрд╣реА compromised workload рддреНрдпрд╛рддреВрди рдЖрд░рд╛рдорд╛рдд рдЬрд╛рдК рд╢рдХрддреЗ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

nat/gateway.py рдордзреАрд▓ NatGateway(name, public_ips) IPs рдЪреА рдпрд╛рджреА рдШреЗрддреЗ. send() рдкрд╣рд┐рд▓рд╛ IP рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рддреЗ; рддреНрдпрд╛ IP рдиреЗ рддреНрдпрд╛ destination рдХрдбреЗ ports_per_dest ports рд╡рд╛рдкрд░рд▓реЗ рдХреА рддреЗ рдкреБрдврдЪреНрдпрд╛ IP рдХрдбреЗ рдЬрд╛рддреЗ. nat/demo.py рдордзреАрд▓ eip() рджреЛрди рдкрддреНрддреЗ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рдлрд╛рдЯрдХрд╛рд╕рд╛рдареА partner allow-list рдЫрд╛рдкрддреЗ. ports_per_dest рд▓рд╣рд╛рди рдХреЗрд▓реНрдпрд╛рдиреЗ рдореВрдарднрд░ connections рдордзреНрдпреЗрдЪ рддреБрдореНрд╣реА рдкреБрдврдЪреНрдпрд╛ IP рдХрдбреЗ рд╕рд╛рдВрдбрдгреЗ рдкрд╛рд╣реВ рд╢рдХрддрд╛.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 nat/demo.py eip
python3 - <<'EOF'
import sys; sys.path.insert(0, "nat"); from gateway import NatGateway
PYPI = ("151.101.0.223", 443, "tcp")
for ips in (["13.232.10.20"], ["13.232.10.20", "13.232.10.30"]):
    g = NatGateway("nat-a", ips, ports_per_dest=3)
    out = [g.send(("10.20.48.25", 40000 + i), PYPI) for i in range(7)]
    print(len(ips), "IP(s):", [r[0] + ":" + str(r[1]) if r else "ERR" for r in out], "┬╖ errors:", g.errors)
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

eip рд╣реЗ partner firewall allow-list: 13.232.10.20/32, 13.232.10.30/32, every desk behind the gate looks like one of these 2 addresses тАФ add IPs (up to 8) for more ports рдЖрдгрд┐ allow-listing the NAT's IP trusts EVERY workload behind it рд╣реА рд╕реВрдЪрдирд╛ рдЫрд╛рдкрддреЗ. рддреБрдордЪрд╛ snippet (55,000 рдРрд╡рдЬреА рдкреНрд░рддреНрдпреЗрдХ destination рд╕рд╛рдареА 3 ports, рдореНрд╣рдгрдЬреЗ screen рд╡рд░ рдорд╛рд╡реЗрд▓) рд╣реЗ рдЫрд╛рдкрддреЛ:

1 IP(s): ['13.232.10.20:1024', '13.232.10.20:1025', '13.232.10.20:1026', 'ERR', 'ERR', 'ERR', 'ERR'] ┬╖ errors: 4
2 IP(s): ['13.232.10.20:1024', '13.232.10.20:1025', '13.232.10.20:1026', '13.232.10.30:1024', '13.232.10.30:1025', '13.232.10.30:1026', 'ERR'] ┬╖ errors: 1

рджреБрд╕рд▒реНрдпрд╛ IP рдиреЗ рдиреЛрдВрджрд╡рд╣реАрддреАрд▓ рдЬрд╛рдЧрд╛ рджреБрдкреНрдкрдЯ рдХреЗрд▓реА. рдЖрдгрд┐ connections 4тАУ6 13.232.10.30 рд╡рд░реВрди рдмрд╛рд╣реЗрд░ рдкрдбрд▓реЗ тАФ рдлрдХреНрдд .20 рдпрд╛рджреАрдд рдЯрд╛рдХрд▓реЗрд▓реНрдпрд╛ partner рдиреЗ рддреЗ рдирд╛рдХрд╛рд░рд▓реЗ рдЕрд╕рддреЗ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рд╕реНрдерд┐рд░ рдкрддреНрддреНрдпрд╛рдореБрд│реЗ partners рддреБрдордЪреА рд╢рд╛рд│рд╛ рдУрд│рдЦреВ рд╢рдХрддрд╛рдд, рдЖрдгрд┐ рдЬрд╛рд╕реНрдд рдкрддреНрддреЗ рдлрд╛рдЯрдХрд╛рд▓рд╛ рдЬрд╛рд╕реНрдд ports рджреЗрддрд╛рдд тАФ рдкрдг рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрддрд╛ partner рдЪреНрдпрд╛ рдпрд╛рджреАрдд рдЕрд╕рд╛рдпрд▓рд╛рдЪ рд╣рд╡рд╛, рдЖрдгрд┐ рддреНрдпрд╛рддрд▓рд╛ рдХреЛрдгрддрд╛рд╣реА рдкрддреНрддрд╛ рдХреЛрдгрддреЗ workload call рдХрд░рдд рдЖрд╣реЗ рддреЗ рд╕рд╛рдВрдЧрдд рдирд╛рд╣реА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ рдЖрдзреАрдкрд╛рд╕реВрди рдЕрд╕рд▓реЗрд▓реНрдпрд╛ public NAT gateway рд▓рд╛ secondary Elastic IP рдЬреЛрдбрд╛, рдЖрдгрд┐ partners рдирд╛ рдкрд╛рдард╡рдгреНрдпрд╛рд╕рд╛рдареА addresses рдЪреА рдпрд╛рджреА рдХрд╛рдврд╛:

aws ec2 allocate-address --domain vpc --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=nat-a-2}]'
aws ec2 associate-nat-gateway-address --nat-gateway-id nat-0abc --allocation-ids eipalloc-0def
aws ec2 describe-nat-gateways --nat-gateway-ids nat-0abc --query 'NatGateways[].NatGatewayAddresses[].[PublicIp,IsPrimary,Status]' --output table

Terraform рдордзреНрдпреЗ, secondary address рд╕рд╣ рддреЛрдЪ gateway:

resource "aws_nat_gateway" "a" {
  allocation_id            = aws_eip.nat_a.id
  secondary_allocation_ids = [aws_eip.nat_a_2.id]
  subnet_id                = aws_subnet.public_a.id
}

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: egress IPs рдЪреА рдпрд╛рджреА рдПрдХрд╛рдЪ рдард┐рдХрд╛рдгреА рдареЗрд╡рд╛ (рдПрдХ output, wiki page, рдПрдХ parameter), address рдЬреЛрдбрдгреНрдпрд╛ рдХрд┐рдВрд╡рд╛ рдмрджрд▓рдгреНрдпрд╛рдЖрдзреА рддреА partners рдирд╛ рдкрд╛рдард╡рд╛, рдЖрдгрд┐ allow-list рд▓рд╛ рдХрдзреАрд╣реА рдПрдХрдореЗрд╡ рддрдкрд╛рд╕рдгреА рд╣реЛрдК рджреЗрдК рдирдХрд╛.

тПня╕П рдкреБрдвреЗ

рдлрд╛рдЯрдХ рдореЛрдлрдд рдирд╛рд╣реА. рддреНрдпрд╛рд▓рд╛ рджреЛрди рдореАрдЯрд░ рдЖрд╣реЗрдд, рдЖрдгрд┐ рддреНрдпрд╛рддрд▓рд╛ рдПрдХ рдЬрд╡рд│рдЬрд╡рд│ рдкреНрд░рддреНрдпреЗрдХ team рд▓рд╛ рдЖрд╢реНрдЪрд░реНрдпрдЪрдХрд┐рдд рдХрд░рддреЛ. рдлрд╛рдЯрдХрд╛рдЪрд╛ рдЦрд░реНрдЪ рдХрд┐рддреА.

git checkout lesson-06-costs

ЁЯУМ Lesson 05 тАФ Elastic IPs & allow-listing

ЁЯУН You are here: Lesson 05 of 8 ┬╖ Previous: lesson-04-per-az-routing ┬╖ Next: lesson-06-costs


ЁЯУж What's in this branch

Lessons 01тАУ04, plus the gate's fixed return address: the Elastic IP. Why partners allow-list it, how a second IP gives the gate a second set of ports, and why an address is not an identity. eip() in nat/demo.py shows it.

ЁЯзТ Explain like I'm 5

Every letter that leaves through the gate has the school's address on the back. A bank in town says: "we only open letters from that address". Good тАФ a stranger in another town cannot pretend to be the school.

But look closely. Every pupil's letter has the same address on the back: the head teacher's, the canteen's, a new pupil's on the first day. The bank has not trusted the head teacher. It has trusted the whole school.

And when the school gets busy, it adds a second gate address. Now the bank must know both, or half the letters go unread.

ЁЯЧ║я╕П Diagram

flowchart LR
    app["ЁЯкС payments app"] --> gate
    web["ЁЯкС web app"] --> gate
    job["ЁЯкС batch job"] --> gate
    gate["ЁЯЪк NAT gateway<br/>ЁЯУМ 13.232.10.20<br/>ЁЯУМ 13.232.10.30"]
    gate -->|"1 from a listed IP"| partner["ЁЯПж partner firewall<br/>allow 13.232.10.20/32<br/>allow 13.232.10.30/32"]
    partner -->|"2 still check who: mTLS / signed request"| ok["тЬЕ"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/nat/lesson-diagrams.html#l05

тЭУ What

ЁЯдФ Why

Because partner firewalls (banks, payment providers, government APIs) often accept traffic only from known addresses. Servers behind a NAT solve that: many changing servers, one or a few fixed addresses. But teams often stop there, and then the allow-list is the only lock тАФ and any compromised workload in the VPC can walk through it.

ЁЯФз How (in this repo)

NatGateway(name, public_ips) in nat/gateway.py takes a list of IPs. send() tries the first IP; when that IP has used ports_per_dest ports to that destination, it moves to the next IP. eip() in nat/demo.py prints the partner allow-list for a gate with two addresses. Shrinking ports_per_dest lets you watch the spill-over with a handful of connections.

ЁЯзк Try it

python3 nat/demo.py eip
python3 - <<'EOF'
import sys; sys.path.insert(0, "nat"); from gateway import NatGateway
PYPI = ("151.101.0.223", 443, "tcp")
for ips in (["13.232.10.20"], ["13.232.10.20", "13.232.10.30"]):
    g = NatGateway("nat-a", ips, ports_per_dest=3)
    out = [g.send(("10.20.48.25", 40000 + i), PYPI) for i in range(7)]
    print(len(ips), "IP(s):", [r[0] + ":" + str(r[1]) if r else "ERR" for r in out], "┬╖ errors:", g.errors)
EOF

тЬЕ Verify тАФ what you should see

eip prints partner firewall allow-list: 13.232.10.20/32, 13.232.10.30/32, every desk behind the gate looks like one of these 2 addresses тАФ add IPs (up to 8) for more ports and the warning allow-listing the NAT's IP trusts EVERY workload behind it. Your snippet (3 ports per destination instead of 55,000, so it fits on a screen) prints:

1 IP(s): ['13.232.10.20:1024', '13.232.10.20:1025', '13.232.10.20:1026', 'ERR', 'ERR', 'ERR', 'ERR'] ┬╖ errors: 4
2 IP(s): ['13.232.10.20:1024', '13.232.10.20:1025', '13.232.10.20:1026', '13.232.10.30:1024', '13.232.10.30:1025', '13.232.10.30:1026', 'ERR'] ┬╖ errors: 1

The second IP doubled the room in the register. And connections 4тАУ6 left from 13.232.10.30 тАФ a partner that listed only .20 would refuse them.

ЁЯПБ What you just proved

A fixed address lets partners recognise your school, and more addresses give the gate more ports тАФ but every address must be on the partner's list, and none of them says which workload is calling.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ add a secondary Elastic IP to an existing public NAT gateway, and list the addresses to send to partners:

aws ec2 allocate-address --domain vpc --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=nat-a-2}]'
aws ec2 associate-nat-gateway-address --nat-gateway-id nat-0abc --allocation-ids eipalloc-0def
aws ec2 describe-nat-gateways --nat-gateway-ids nat-0abc --query 'NatGateways[].NatGatewayAddresses[].[PublicIp,IsPrimary,Status]' --output table

In Terraform, the same gateway with a secondary address:

resource "aws_nat_gateway" "a" {
  allocation_id            = aws_eip.nat_a.id
  secondary_allocation_ids = [aws_eip.nat_a_2.id]
  subnet_id                = aws_subnet.public_a.id
}

ЁЯПн Why this matters in production: keep the list of egress IPs in one place (an output, a wiki page, a parameter), send it to partners before you add or change an address, and never let the allow-list be the only check.

тПня╕П Next

The gate is not free. It has two meters, and one of them surprises almost every team. What the gate costs.

git checkout lesson-06-costs
тЖР Previousper az routingNext тЖТcosts

This page is the lesson's README from the lesson-05-elastic-ips branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.