ЁЯПл The SchoolтА║ЁЯЫОя╕П API GatewayтА║ЁЯП╖я╕П рдзрдбрд╛ 10 тАФ Custom domains рдЖрдгрд┐ TLS: рджрд╛рд░рд╛рд╡рд░рдЪрд╛ рдирд╛рд╡рдлрд▓рдХ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯП╖я╕П рдзрдбрд╛ 10 тАФ Custom domains рдЖрдгрд┐ TLS: рджрд╛рд░рд╛рд╡рд░рдЪрд╛ рдирд╛рд╡рдлрд▓рдХ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 10 ┬╖ рдорд╛рдЧреЗ: lesson-09-cors ┬╖ рдкреБрдвреЗ: lesson-11-monitoring


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

рдзрдбреЗ 01тАУ09, рдЖрдгрд┐ office рд╕рд╛рдареА рдЦрд░реЗ рдирд╛рд╡: AWS рдЪреНрдпрд╛ рдпрд╛рджреГрдЪреНрдЫрд┐рдХ рдкрддреНрддреНрдпрд╛рдРрд╡рдЬреА api.school.example тАФ ACM certificate (рдЖрдгрд┐ рддреЗ рдХреЛрдгрддреНрдпрд╛ Region рдордзреНрдпреЗ рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ), DNS, base path mappings (/v1, /v2), рддреАрди endpoint types, рдХрд┐рдорд╛рди TLS version рдЖрдгрд┐ mutual TLS. apigw/demo.py рдордзрд▓реЗ domains() checklist рдЫрд╛рдкрддреЗ, рдЖрдгрд┐ рддреБрдореНрд╣реА lab office рд╕рдореЛрд░ рдПрдХ рдЫреЛрдЯреЗ base path mapping рдмрд╛рдВрдзрддрд╛.

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

Office рдЪрд╛ рдЦрд░рд╛ рдкрддреНрддрд╛ рдореНрд╣рдгрдЬреЗ рдПрдХ рд▓рд╛рдВрдмрд▓рдЪрдХ code: abc123.execute-api.ap-south-1.amazonaws.com. рддреЛ рдХреЛрдгрд╛рд▓рд╛рдЪ рд▓рдХреНрд╖рд╛рдд рд░рд╛рд╣рдд рдирд╛рд╣реА, рдЖрдгрд┐ рд╢рд╛рд│реЗрдиреЗ рдирд╡реЗ office рдмрд╛рдВрдзрд▓реЗ рддрд░ code рдмрджрд▓рддреЛ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рд▓рдХрд╛рд▓рд╛ рдирд╡рд╛ code рд╢рд┐рдХрд╛рд╡рд╛ рд▓рд╛рдЧрддреЛ.

рдореНрд╣рдгреВрди рд╢рд╛рд│рд╛ рдлрд╛рдЯрдХрд╛рд╡рд░ рдПрдХ рдирд╛рд╡рдлрд▓рдХ ЁЯП╖я╕П рд▓рд╛рд╡рддреЗ: api.school.example. рдирд╛рд╡рдлрд▓рдХрд╛рдЪреНрдпрд╛ рдорд╛рдЧреЗ рджреЛрди рджрд╛рд░реЗ рдЕрд╕реВ рд╢рдХрддрд╛рдд: /v1 рдЬреБрдиреНрдпрд╛ office рдХрдбреЗ рдЬрд╛рддреЗ рдЖрдгрд┐ /v2 рдирд╡реНрдпрд╛ office рдХрдбреЗ. рдкрд╛рд▓рдХрд╛рдВрд╕рд╛рдареА рдирд╛рд╡ рдХрд╛рдпрдо рддреЗрдЪ рд░рд╛рд╣рддреЗ.

рдирд╛рд╡рдлрд▓рдХ рдЦрд░реЛрдЦрд░ рд╢рд╛рд│реЗрдЪрд╛рдЪ рдЖрд╣реЗ рд╣реЗ рд╕рд┐рджреНрдз рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдлрд╛рдЯрдХ рдПрдХ certificate рджрд╛рдЦрд╡рддреЗ тАФ рд╡рд┐рд╢реНрд╡рд╛рд╕реВ office рдиреЗ рд╕рд╣реА рдХреЗрд▓реЗрд▓рд╛ рдХрд╛рдЧрдж (TLS certificate). рдХрд╛рд╣реА partner рд╢рд╛рд│рд╛рдВрдирд╛ рдЖрдд рдпреЗрдгреНрдпрд╛рдЖрдзреА рддреНрдпрд╛рдВрдЪреЗ рд╕реНрд╡рддрдГрдЪреЗ certificate рд╕реБрджреНрдзрд╛ рджрд╛рдЦрд╡рд╛рд╡реЗ рд▓рд╛рдЧрддреЗ тАФ рд╣реЗрдЪ mutual TLS.

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

flowchart LR
    user["ЁЯЩЛ https://api.school.example/v1/students/7"] --> dns["ЁЯУЦ Route 53 alias<br/>api.school.example"]
    dns --> dom["ЁЯП╖я╕П custom domain<br/>ACM certificate ┬╖ TLS 1.2 minimum"]
    dom -->|"base path /v1"| a1["ЁЯЫОя╕П school-api ┬╖ prod"]
    dom -->|"base path /v2"| a2["ЁЯЫОя╕П school-api-v2 ┬╖ prod"]
    dom -.->|"no mapping for /students/7"| no["ЁЯЪл 403"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдкрддреНрддрд╛ рдореНрд╣рдгрдЬреЗ рдкреНрд░рддреНрдпреЗрдХ client рд▓рд╛ рджрд┐рд▓реЗрд▓реЗ рд╡рдЪрди. Custom domain рдореБрд│реЗ рддреБрдореНрд╣реА рдирд╡реНрдпрд╛ API рдХрдбреЗ, рдирд╡реНрдпрд╛ Region рдХрдбреЗ рдХрд┐рдВрд╡рд╛ рдирд╡реНрдпрд╛ рдкреНрд░рдХрд╛рд░рд╛рдХрдбреЗ (REST рддреЗ HTTP) рдЬрд╛рдК рд╢рдХрддрд╛, рдХреЛрдгрд╛рд▓рд╛рд╣реА рддреНрдпрд╛рдВрдЪрд╛ code рдмрджрд▓рд╛рдпрд▓рд╛ рди рд╕рд╛рдВрдЧрддрд╛. Base paths рдореБрд│реЗ clients рд╣рд│реВрд╣рд│реВ рд╕рд░рдХрдд рдЕрд╕рддрд╛рдирд╛ v1 рдЖрдгрд┐ v2 рд╢реЗрдЬрд╛рд░реА рд╢реЗрдЬрд╛рд░реА рд░рд╛рд╣реВ рд╢рдХрддрд╛рдд. рдЖрдгрд┐ TLS settings рдард░рд╡рддрд╛рдд рдХреА рдХреЛрдгрддреЗ рдЬреБрдиреЗ, рдХрдордХреБрд╡рдд clients рддреБрдореНрд╣реА рдЕрдЬреВрдирд╣реА рд╕реНрд╡реАрдХрд╛рд░рддрд╛.

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

Lab рдордзреНрдпреЗ DNS рдХрд┐рдВрд╡рд╛ certificates рдирд╛рд╣реАрдд, рдореНрд╣рдгреВрди apigw/demo.py рдордзрд▓реЗ domains() checklist рдЫрд╛рдкрддреЗ. рддреБрдореНрд╣реА рдЪрд╛рд▓рд╡реВ рд╢рдХрддрд╛ рддреЛ рднрд╛рдЧ рдореНрд╣рдгрдЬреЗ base path mapping: Gateway.handle() рд╕рдореЛрд░рдЪреЗ рдПрдХ рдЫреЛрдЯреЗрд╕реЗ function рдЬреЗ path рдЪреНрдпрд╛ рдкрд╣рд┐рд▓реНрдпрд╛ рднрд╛рдЧрд╛рд╡рд░реВрди API рдЖрдгрд┐ stage рдирд┐рд╡рдбрддреЗ, рддреЛ рднрд╛рдЧ рдХрд╛рдвреВрди рдЯрд╛рдХрддреЗ, рдЖрдгрд┐ рдЙрд░рд▓реЗрд▓реЗ рдкреБрдвреЗ рдкрд╛рдард╡рддреЗ тАФ custom domain рдЪреЗ mappings рдиреЗрдордХреЗ рд╣реЗрдЪ рдХрд╛рдо рдХрд░рддрд╛рдд.

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

python3 apigw/demo.py domains
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office
v1, _ = office()
MAPPINGS = {"/v1": (v1, "prod"), "/v1-dev": (v1, "dev")}      # api.school.example/<base path> тЖТ (API, stage)
def custom_domain(method, url_path):
    for base in sorted(MAPPINGS, key=len, reverse=True):
        if url_path.startswith(base + "/"):
            api, stage = MAPPINGS[base]
            return api.handle(method, url_path[len(base):], stage=stage)
    return 403, {}, {"message": "Forbidden"}
for p in ("/v1/students/7", "/v1-dev/students/9", "/v2/students/7", "/students/7"):
    st, _, out = custom_domain("GET", p); print(f"api.school.example{p:<18} тЖТ {st} {out}")
EOF

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

domains рдкрд╛рдЪ рдУрд│реА рдЫрд╛рдкрддреЗ, рддреНрдпрд╛рдВрдкреИрдХреА certificate ACM тАФ in us-east-1 for edge-optimized, in the API's Region for regional, base path mapping api.school.example/v1 тЖТ school-api prod ┬╖ /v2 тЖТ school-api-v2 prod рдЖрдгрд┐ TLS security policy TLS 1.2 minimum ┬╖ mutual TLS for partners with client certificates.

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

api.school.example/v1/students/7     тЖТ 200 {'id': '7', 'name': 'Aishwarya', 'class': '8B'}
api.school.example/v1-dev/students/9 тЖТ 200 {'id': '9', 'name': 'Katrina', 'class': '9A'}
api.school.example/v2/students/7     тЖТ 403 {'message': 'Forbidden'}
api.school.example/students/7        тЖТ 403 {'message': 'Forbidden'}

/v2 рд▓рд╛ рдЕрдЬреВрди mapping рдирд╛рд╣реА, рдЖрдгрд┐ base path рдирд╕рд▓реЗрд▓рд╛ path рдХрд╢рд╛рд╢реАрдЪ рдЬреБрд│рдд рдирд╛рд╣реА.

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

рдПрдХ рдирд╛рд╡ рдЕрдиреЗрдХ APIs рдЖрдгрд┐ stages рдХрдбреЗ рдиреЗрдК рд╢рдХрддреЗ; base path рджрд╛рд░ рдирд┐рд╡рдбрддреЛ, рдЖрдгрд┐ рдорд╛рдЧрдЪреНрдпрд╛ API рд▓рд╛ base path рдХрдзреА рджрд┐рд╕рддрдЪ рдирд╛рд╣реА.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ TLS 1.2 рд╕рд╣ regional custom domain, /v1 mapping, рдЖрдгрд┐ DNS:

aws apigateway create-domain-name --domain-name api.school.example \
    --regional-certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/1234abcd-12ab-34cd-56ef-1234567890ab \
    --endpoint-configuration types=REGIONAL --security-policy TLS_1_2
aws apigateway create-base-path-mapping --domain-name api.school.example \
    --rest-api-id abc123 --stage prod --base-path v1
aws apigateway get-domain-name --domain-name api.school.example \
    --query '{target:regionalDomainName,zone:regionalHostedZoneId}'

Partners рд╕рд╛рдареА mutual TLS (S3 рдордзреНрдпреЗ truststore):

aws apigateway create-domain-name --domain-name partners.school.example \
    --regional-certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/1234abcd-12ab-34cd-56ef-1234567890ab \
    --endpoint-configuration types=REGIONAL --security-policy TLS_1_2 \
    --mutual-tls-authentication truststoreUri=s3://school-truststore/partners.pem

Terraform (HTTP API mapping):

resource "aws_apigatewayv2_api_mapping" "v1" {
  api_id          = aws_apigatewayv2_api.school.id
  domain_name     = aws_apigatewayv2_domain_name.api.id
  stage           = aws_apigatewayv2_stage.prod.id
  api_mapping_key = "v1"
}

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ: рдлрдХреНрдд custom domain рдкреНрд░рд╕рд┐рджреНрдз рдХрд░рд╛, clients рд╕рд░рдХрд▓реНрдпрд╛рд╡рд░ default endpoint disable рдХрд░рд╛, рдХрд┐рдорд╛рди TLS 1.2 рдареЗрд╡рд╛, рдЖрдгрд┐ ACM рд▓рд╛ certificate renew рдХрд░реВ рджреНрдпрд╛ тАФ validation рдХрдзреА рдЕрдкрдпрд╢реА рдард░рд▓реЗ рддрд░ alarm рд╕рд╣.

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

Office рд▓рд╛ рдирд╛рд╡ рдорд┐рд│рд╛рд▓реЗ. рдЖрддрд╛: рддреЗ рдирд┐рд░реЛрдЧреА рдЖрд╣реЗ рд╣реЗ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╕реЗ рдХрд│рдгрд╛рд░? Dashboard рдЖрдгрд┐ stopwatch тАФ monitoring.

git checkout lesson-11-monitoring

ЁЯП╖я╕П Lesson 10 тАФ Custom domains & TLS: the name plate on the door

ЁЯУН You are here: Lesson 10 of 12 ┬╖ Previous: lesson-09-cors ┬╖ Next: lesson-11-monitoring


ЁЯУж What's in this branch

Lessons 01тАУ09, plus a real name for the office: api.school.example instead of a random AWS address тАФ the ACM certificate (and which Region it must live in), DNS, base path mappings (/v1, /v2), the three endpoint types, the minimum TLS version and mutual TLS. domains() in apigw/demo.py prints the checklist, and you build a small base path mapping in front of the lab office.

ЁЯзТ Explain like I'm 5

The office's real address is a long code: abc123.execute-api.ap-south-1.amazonaws.com. Nobody can remember it, and if the school builds a new office, the code changes and every parent must learn a new one.

So the school puts a name plate ЁЯП╖я╕П on the gate: api.school.example. Behind the name plate there can be two doors: /v1 leads to the old office and /v2 to the new one. Parents keep the same name forever.

To prove the name plate is really the school's, the gate shows a certificate тАФ a paper signed by a trusted office (the TLS certificate). Some partner schools must show their own certificate too before they come in тАФ that is mutual TLS.

ЁЯЧ║я╕П Diagram

flowchart LR
    user["ЁЯЩЛ https://api.school.example/v1/students/7"] --> dns["ЁЯУЦ Route 53 alias<br/>api.school.example"]
    dns --> dom["ЁЯП╖я╕П custom domain<br/>ACM certificate ┬╖ TLS 1.2 minimum"]
    dom -->|"base path /v1"| a1["ЁЯЫОя╕П school-api ┬╖ prod"]
    dom -->|"base path /v2"| a2["ЁЯЫОя╕П school-api-v2 ┬╖ prod"]
    dom -.->|"no mapping for /students/7"| no["ЁЯЪл 403"]

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

тЭУ What

ЁЯдФ Why

Because an address is a promise to every client. A custom domain lets you move to a new API, a new Region or a new kind (REST to HTTP) without asking anyone to change their code. Base paths let v1 and v2 live side by side while clients move. And the TLS settings decide which old, weak clients you still accept.

ЁЯФз How (in this repo)

The lab has no DNS or certificates, so domains() in apigw/demo.py prints the checklist. The part you can run is a base path mapping: a tiny function in front of Gateway.handle() that picks the API and stage from the first path part, strips it, and forwards the rest тАФ the same job a custom domain's mappings do.

ЁЯзк Try it

python3 apigw/demo.py domains
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office
v1, _ = office()
MAPPINGS = {"/v1": (v1, "prod"), "/v1-dev": (v1, "dev")}      # api.school.example/<base path> тЖТ (API, stage)
def custom_domain(method, url_path):
    for base in sorted(MAPPINGS, key=len, reverse=True):
        if url_path.startswith(base + "/"):
            api, stage = MAPPINGS[base]
            return api.handle(method, url_path[len(base):], stage=stage)
    return 403, {}, {"message": "Forbidden"}
for p in ("/v1/students/7", "/v1-dev/students/9", "/v2/students/7", "/students/7"):
    st, _, out = custom_domain("GET", p); print(f"api.school.example{p:<18} тЖТ {st} {out}")
EOF

тЬЕ Verify тАФ what you should see

domains prints five rows, among them certificate ACM тАФ in us-east-1 for edge-optimized, in the API's Region for regional, base path mapping api.school.example/v1 тЖТ school-api prod ┬╖ /v2 тЖТ school-api-v2 prod and TLS security policy TLS 1.2 minimum ┬╖ mutual TLS for partners with client certificates.

Your snippet prints:

api.school.example/v1/students/7     тЖТ 200 {'id': '7', 'name': 'Aishwarya', 'class': '8B'}
api.school.example/v1-dev/students/9 тЖТ 200 {'id': '9', 'name': 'Katrina', 'class': '9A'}
api.school.example/v2/students/7     тЖТ 403 {'message': 'Forbidden'}
api.school.example/students/7        тЖТ 403 {'message': 'Forbidden'}

/v2 has no mapping yet, and a path with no base path matches nothing.

ЁЯПБ What you just proved

One name can lead to many APIs and stages; the base path picks the door, and the API behind it never sees the base path.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ a regional custom domain with TLS 1.2, a /v1 mapping, and DNS:

aws apigateway create-domain-name --domain-name api.school.example \
    --regional-certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/1234abcd-12ab-34cd-56ef-1234567890ab \
    --endpoint-configuration types=REGIONAL --security-policy TLS_1_2
aws apigateway create-base-path-mapping --domain-name api.school.example \
    --rest-api-id abc123 --stage prod --base-path v1
aws apigateway get-domain-name --domain-name api.school.example \
    --query '{target:regionalDomainName,zone:regionalHostedZoneId}'

Mutual TLS for partners (truststore in S3):

aws apigateway create-domain-name --domain-name partners.school.example \
    --regional-certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/1234abcd-12ab-34cd-56ef-1234567890ab \
    --endpoint-configuration types=REGIONAL --security-policy TLS_1_2 \
    --mutual-tls-authentication truststoreUri=s3://school-truststore/partners.pem

Terraform (HTTP API mapping):

resource "aws_apigatewayv2_api_mapping" "v1" {
  api_id          = aws_apigatewayv2_api.school.id
  domain_name     = aws_apigatewayv2_domain_name.api.id
  stage           = aws_apigatewayv2_stage.prod.id
  api_mapping_key = "v1"
}

ЁЯПн Why this matters in production: publish only the custom domain, disable the default endpoint once clients have moved, set TLS 1.2 as the minimum, and let ACM renew the certificate тАФ with an alarm if validation ever fails.

тПня╕П Next

The office has a name. Now: how do you know it is healthy? The dashboard and the stopwatch тАФ monitoring.

git checkout lesson-11-monitoring
тЖР PreviouscorsNext тЖТmonitoring

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