ЁЯПл The SchoolтА║ЁЯЫбя╕П SecurityтА║ЁЯФТ рдзрдбрд╛ 09 тАФ рдкреНрд░рд╡рд╛рд╕рд╛рддрд▓реЗ рдЖрдгрд┐ рд╕рд╛рдард╡рд▓реЗрд▓реЗ encryption: рд╕реАрд▓ рдХреЗрд▓реЗрд▓реА рдкрд╛рдХрд┐рдЯреЗ рдЖрдгрд┐ рдХреБрд▓реВрдкрдмрдВрдж рдХрдкрд╛рдЯреЗ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФТ рдзрдбрд╛ 09 тАФ рдкреНрд░рд╡рд╛рд╕рд╛рддрд▓реЗ рдЖрдгрд┐ рд╕рд╛рдард╡рд▓реЗрд▓реЗ encryption: рд╕реАрд▓ рдХреЗрд▓реЗрд▓реА рдкрд╛рдХрд┐рдЯреЗ рдЖрдгрд┐ рдХреБрд▓реВрдкрдмрдВрдж рдХрдкрд╛рдЯреЗ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 16 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреАрд▓: lesson-08-iam-network ┬╖ рдкреБрдвреАрд▓: lesson-10-k8s-rbac


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

рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ рд╢реЗрд╡рдЯрдЪреЗ cloud рдирд┐рдпрдВрддреНрд░рдг: encryption. рдЬреЛ data рдлрд┐рд░рддреЛ (browser рдЖрдгрд┐ рд╢рд╛рд│реЗрдЪреА website рдпрд╛рдВрдЪреНрдпрд╛рдд, app рдЖрдгрд┐ database рдпрд╛рдВрдЪреНрдпрд╛рдд) рддреЛ TLS рдЪреНрдпрд╛ рдЖрдд рдкреНрд░рд╡рд╛рд╕ рдХрд░рддреЛ. рдЬреЛ data рдкрдбреВрди рд░рд╛рд╣рддреЛ (disks, buckets, database files, backups) рддреЛ KMS key рдиреЗ encrypt рдХрд░реВрди рд╕рд╛рдард╡рд▓рд╛ рдЬрд╛рддреЛ. рдХреЛрдгрддреНрдпрд╛ TLS versions рдирд╛ рдкрд░рд╡рд╛рдирдЧреА рджреНрдпрд╛рдпрдЪреА, certificate рдХрд╛рдп рд╕рд┐рджреНрдз рдХрд░рддреЗ, рддреЗ рдирд╛рд╡рд╛рд╢реА рдХрд╛ рдЬреБрд│рд╛рдпрд▓рд╛ рд╣рд╡реЗ рдЖрдгрд┐ expire рдХрд╛ рд╡реНрд╣рд╛рдпрд▓рд╛ рдирдХреЛ, HSTS рдХрд╛рдп рднрд░ рдШрд╛рд▓рддреЗ, рдЖрдгрд┐ KMS рд╕реЛрдмрдд encryption at rest рдХрд╕реЗ рдЪрд╛рд▓рддреЗ, рд╣реЗ рддреБрдореНрд╣реА рд╢рд┐рдХрддрд╛.

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

рд╢рд╛рд│рд╛ рдкрд╛рд▓рдХрд╛рдВрдирд╛ рдкреНрд░рдЧрддрд┐рдкреБрд╕реНрддрдХреЗ рдкрд╛рдард╡рддреЗ. рджреЛрди рдзреЛрдХреЗ:

  1. рд░рд╕реНрддреНрдпрд╛рд╡рд░. рд╡рд╛рдЯреЗрдд рдкрддреНрд░ рд╣рд╛рддрд╛рдд рдзрд░рдгрд╛рд░рд╛ рдХреЛрдгреАрд╣реА рддреЗ рдЙрдШрдбреВ рд╢рдХрддреЛ. рдореНрд╣рдгреВрди рдХрд╛рд░реНрдпрд╛рд▓рдп рдкреНрд░рддреНрдпреЗрдХ рдкрддреНрд░ рдПрдХрд╛ рд╕реАрд▓ рдХреЗрд▓реЗрд▓реНрдпрд╛ рдкрд╛рдХрд┐рдЯрд╛рдд ЁЯФТ рдШрд╛рд▓рддреЗ рдЬреЗ рдлрдХреНрдд рдкрд╛рд▓рдХрдЪ рдЙрдШрдбреВ рд╢рдХрддрд╛рдд. рд╣реЗрдЪ encryption in transit (TLS).
  2. рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд. рд░рд╛рддреНрд░реА рдХреЛрдгреАрддрд░реА рдлрд╛рдпрд▓реАрдВрдЪреЗ рдХрдкрд╛рдЯ рдЙрдШрдбреВ рд╢рдХрддреЗ. рдореНрд╣рдгреВрди рдХрдкрд╛рдЯ рдХреБрд▓реВрдкрдмрдВрдж рдЕрд╕рддреЗ, рдЖрдгрд┐ рддреНрдпрд╛рдЪреА рдЪрд╛рд╡реА рдПрдХрд╛ рдЪрд╛рд╡реА-рддрд┐рдЬреЛрд░реАрдд ЁЯЧЭя╕П рдЕрд╕рддреЗ (рдзрдбрд╛ 07). рд╣реЗрдЪ encryption at rest (KMS).

рдЖрдгрд┐ рдкрд╛рдХреАрдЯ рдЖрдгрдгрд╛рд░рд╛ рдХреБрд░рд┐рдпрд░ рдПрдХ рдУрд│рдЦрдкрддреНрд░ рдмрд┐рд▓реНрд▓рд╛ рджрд╛рдЦрд╡рддреЛ. рд╕реБрд░рдХреНрд╖рд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рддрд▓реА рджреАрдкрд┐рдХрд╛ рдмрд┐рд▓реНрд▓реНрдпрд╛рд╡рд░рдЪреНрдпрд╛ рддреАрди рдЧреЛрд╖реНрдЯреА рддрдкрд╛рд╕рддреЗ:

1990 рдЪреНрдпрд╛ рджрд╢рдХрд╛рддрд▓реНрдпрд╛ рдЬреБрдиреНрдпрд╛ рдкрд╛рдХрд┐рдЯрд╛рдВрдЪреЗ рд╕реАрд▓ рдХрдордХреБрд╡рдд рдЕрд╕рддрд╛рдд. рдореНрд╣рдгреВрди рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪрд╛ рдЖрдгрдЦреА рдПрдХ рдирд┐рдпрдо рдЖрд╣реЗ: "рдлрдХреНрдд рдирд╡реА рдкрд╛рдХрд┐рдЯреЗ тАФ TLS 1.2 рдХрд┐рдВрд╡рд╛ 1.3." рдЖрдгрд┐ рдкреБрдврдЪреНрдпрд╛ рджрд╛рд░рд╛рд╡рд░ рдПрдХ рдкрд╛рдЯреА рд▓рд┐рд╣рд┐рд▓реЗрд▓реА рдЕрд╕рддреЗ: "рдиреЗрд╣рдореА рд╕реАрд▓-рдкрд╛рдХрд┐рдЯрд╛рдЪреЗ рджрд╛рд░ рд╡рд╛рдкрд░рд╛, рдЙрдШрдбреЗ рджрд╛рд░ рдХрдзреАрдЪ рдирд╛рд╣реА." рддреА рдкрд╛рдЯреА рдореНрд╣рдгрдЬреЗ HSTS.

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

flowchart LR
    p["ЁЯСк parent's browser"] -->|"ЁЯФТ TLS 1.2 / 1.3<br/>certificate: name тЬУ ┬╖ expiry тЬУ ┬╖ issuer тЬУ"| lb["тЪЦя╕П load balancer / CloudFront<br/>ACM certificate ┬╖ HSTS"]
    lb -->|"ЁЯФТ TLS again inside the VPC"| app["ЁЯН│ results API"]
    app -->|"ЁЯФТ TLS (rds.force_ssl)"| db["ЁЯУТ database<br/>ЁЯЧДя╕П encrypted at rest"]
    db --- k["ЁЯЧЭя╕П KMS key<br/>disks ┬╖ buckets ┬╖ backups"]
    bad["тЭМ api.school.example<br/>TLS 1.0 ┬╖ 6 days left ┬╖ wrong name"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рджреЛрди computers рдордзрд▓реЗ network рдЗрддрд░ рд▓реЛрдХрд╛рдВрдЪреЗ рдЕрд╕рддреЗ: caf├й рдордзрд▓реЗ Wi-Fi, рдПрдЦрд╛рджрд╛ internet provider, рд╡рд╛рдЯреВрди рд╡рд╛рдкрд░рд▓реЗрд▓реЗ data centre. TLS рдирд╕реЗрд▓ рддрд░ password рдХрд┐рдВрд╡рд╛ grade рд╡рд╛рдЯреЗрдд рд╡рд╛рдЪрд▓рд╛ рдХрд┐рдВрд╡рд╛ рдмрджрд▓рд▓рд╛ рдЬрд╛рдК рд╢рдХрддреЛ. рдЖрдгрд┐ рдЪреЛрд░рд▓реЗрд▓реА disk, public snapshot рдХрд┐рдВрд╡рд╛ leak рдЭрд╛рд▓реЗрд▓рд╛ backup рд╣реА рд╕рдорд╕реНрдпрд╛ рддреЗрд╡реНрд╣рд╛рдЪ рдЕрд╕рддреЗ рдЬреЗрд╡реНрд╣рд╛ рдЖрддрд▓рд╛ data рд╡рд╛рдЪрддрд╛ рдпреЗрдгреНрдпрд╛рдЬреЛрдЧрд╛ рдЕрд╕рддреЛ. рддреБрдордЪреНрдпрд╛ рдирд┐рдпрдВрддреНрд░рдгрд╛рддрд▓реНрдпрд╛ key рдиреЗ рдХреЗрд▓реЗрд▓реЗ encryption at rest "рдХреЛрдгреАрддрд░реА disk copy рдХреЗрд▓реА" рдпрд╛рдЪреЗ рд░реВрдкрд╛рдВрддрд░ "рдХреЛрдгреАрддрд░реА рдирд┐рд░рд░реНрдердХ рдЧреЛрдВрдЧрд╛рдЯ copy рдХреЗрд▓рд╛" рдпрд╛рдд рдХрд░рддреЗ. рдЪреБрдХреАрдЪреЗ certificate рдирд╛рд╡ рдХрд┐рдВрд╡рд╛ expire рдЭрд╛рд▓реЗрд▓реЗ certificate рд╣реА рдЫреЛрдЯреА рд╕реВрдЪрдирд╛ рджреЗрдЦреАрд▓ рдирд╛рд╣реА: рддреНрдпрд╛рдореБрд│реЗ рд▓реЛрдХрд╛рдВрдирд╛ "рддрд░реАрд╣реА рдкреБрдвреЗ рдЬрд╛" рд╡рд░ click рдХрд░рдгреНрдпрд╛рдЪреА рд╕рд╡рдп рд▓рд╛рдЧрддреЗ, рдЖрдгрд┐ attacker рд▓рд╛ рдиреЗрдордХреЗ рддреЗрдЪ рд╣рд╡реЗ рдЕрд╕рддреЗ.

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

sec/cloud.py рдордзрд▓реЗ tls_ok(cfg) host, cert_names, min_version рдЖрдгрд┐ days_to_expiry рдЕрд╕рд▓реЗрд▓реА configuration рдШреЗрддреЗ, рдЖрдгрд┐ (ok, problems) рдкрд░рдд рдХрд░рддреЗ. рддреЗ flag рдХрд░рддреЗ:

sec/demo.py рдордзрд▓реЗ tls() рджреЛрди sites рддрдкрд╛рд╕рддреЗ: school.example (рдЪрд╛рдВрдЧрд▓реА) рдЖрдгрд┐ api.school.example (рдПрдХрд╛рдЪ рд╡реЗрд│реА рддреАрди рд╕рдорд╕реНрдпрд╛). рд╣реЗ model рдирд╛рд╡реЗ рдиреЗрдордХреА рдЬреБрд│рд╡рддреЗ тАФ рддреНрдпрд╛рд▓рд╛ wildcard рдирд┐рдпрдо рдорд╛рд╣реАрдд рдирд╛рд╣реАрдд. рдЦрд░рд╛ browser results.school.example рд╕рд╛рдареА *.school.example рд╕реНрд╡реАрдХрд╛рд░реЗрд▓. рддреЗ рддреБрдореНрд╣рд╛рд▓рд╛ snippet рдордзреНрдпреЗ рджрд┐рд╕реЗрд▓.

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

python3 sec/demo.py tls
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import tls_ok
base = dict(host="results.school.example", cert_names=["results.school.example"], min_version=1.2, days_to_expiry=60)
for label, change in (("as set up", {}), ("TLS 1.3 only", dict(min_version=1.3)), ("TLS 1.1 allowed", dict(min_version=1.1)),
                      ("expires in 14 days", dict(days_to_expiry=14)), ("expires in 13 days", dict(days_to_expiry=13)),
                      ("wildcard certificate", dict(cert_names=["*.school.example"]))):
    ok, problems = tls_ok({**base, **change})
    print(f"{label:<22} тЖТ {'ok' if ok else problems}")
EOF
python3 sec/test_sec.py

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

tls рд╣реЗ рдЫрд╛рдкрддреЗ:

тХРтХРтХР tls тХРтХРтХР
тФАтФА school.example       тЖТ ok
тФАтФА api.school.example   тЖТ TLS 1.0 allowed тАФ require 1.2+; certificate expires in 6 days; certificate name does not match the host
   in transit: TLS 1.2+ everywhere, HSTS ┬╖ at rest: encrypt disks, buckets, backups with KMS keys ┬╖ rotate certificates automatically

тЬЕ done тАФ every door checked

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

as set up              тЖТ ok
TLS 1.3 only           тЖТ ok
TLS 1.1 allowed        тЖТ ['TLS 1.1 allowed тАФ require 1.2+']
expires in 14 days     тЖТ ok
expires in 13 days     тЖТ ['certificate expires in 13 days']
wildcard certificate   тЖТ ['certificate name does not match the host']

рд╢реЗрд╡рдЯрдЪреНрдпрд╛ рдУрд│реАрдд model browser рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдХрдбрдХ рдЖрд╣реЗ: рдЦрд░рд╛ client results.school.example рд╕рд╛рдареА *.school.example рд╕реНрд╡реАрдХрд╛рд░рддреЛ. Tests рдордзреНрдпреЗ тЬЕ L09 old TLS and near-expiry certificates are flagged рдЖрд╣реЗ рдЖрдгрд┐ рд╢реЗрд╡рдЯ 16/16 passed рдиреЗ рд╣реЛрддреЛ.

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

рдПрдХрд╛ site рдордзреНрдпреЗ рддреАрди рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ TLS рд╕рдорд╕реНрдпрд╛ рдЕрд╕реВ рд╢рдХрддрд╛рдд, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдПрдХ рд╡рд┐рд╢реНрд╡рд╛рд╕ рддреЛрдбрд╛рдпрд▓рд╛ рдкреБрд░реЗрд╢реА рдЖрд╣реЗ: рдЬреБрдирд╛ protocol, рд▓рд╡рдХрд░рдЪ expire рд╣реЛрдгрд╛рд░реЗ certificate, рдЖрдгрд┐ рди рдЬреБрд│рдгрд╛рд░реЗ рдирд╛рд╡. рд╣реА рддрдкрд╛рд╕рдгреА рдЫреЛрдЯреА рдЖрдгрд┐ рдЬрд▓рдж рдЖрд╣реЗ, рдореНрд╣рдгреВрди рддреА рдкреНрд░рддреНрдпреЗрдХ public endpoint рдкрд╛рд╣рдгрд╛рд▒реНрдпрд╛ scheduled job рдордзреНрдпреЗ рд╣рд╡реА, рдХреЛрдгрд╛рдЪреНрдпрд╛ рддрд░реА рдЖрдард╡рдгреАрдд рдирд╛рд╣реА. Model рд╕рд╛рдареА рдлрдХреНрдд 1.3 рд▓рд╛ рдкрд░рд╡рд╛рдирдЧреА рджреЗрдгреЗ рдареАрдХ рдЖрд╣реЗ; рдкреНрд░рддреНрдпрдХреНрд╖рд╛рдд рдкреНрд░рддреНрдпреЗрдХ client 1.3 support рдХрд░рддреЛ рд╣реЗ рдХрд│реЗрдкрд░реНрдпрдВрдд 1.2 рд╕реБрджреНрдзрд╛ рдареЗрд╡рд╛.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ ACM рдХрдбреВрди certificate, DNS рджреНрд╡рд╛рд░реЗ validated (рддреЗ рд╡рд╛рдкрд░рд╛рдд рдЕрд╕реЗрдкрд░реНрдпрдВрдд ACM рддреЗ рдЖрдкреЛрдЖрдк renew рдХрд░рддреЛ):

aws acm request-certificate --domain-name school.example \
    --subject-alternative-names www.school.example api.school.example \
    --validation-method DNS
aws acm describe-certificate --certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/0a1b2c3d-1111-2222-3333-444455556666 \
    --query 'Certificate.{status:Status,names:SubjectAlternativeNames,notAfter:NotAfter,renewal:RenewalEligibility}'

рдПрдХ ALB listener рдЬреЛ рдлрдХреНрдд TLS 1.3 рдЖрдгрд┐ 1.2 рд▓рд╛ рдкрд░рд╡рд╛рдирдЧреА рджреЗрддреЛ, рдЖрдгрд┐ рдПрдХ port-80 listener рдЬреЛ рдлрдХреНрдд redirect рдХрд░рддреЛ:

aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:ap-south-1:111122223333:loadbalancer/app/school-alb/50dc6c495c0c9188 \
    --protocol HTTPS --port 443 \
    --certificates CertificateArn=arn:aws:acm:ap-south-1:111122223333:certificate/0a1b2c3d-1111-2222-3333-444455556666 \
    --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
    --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:ap-south-1:111122223333:targetgroup/results-api/6d0ecf831eec9f09
aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:ap-south-1:111122223333:loadbalancer/app/school-alb/50dc6c495c0c9188 \
    --protocol HTTP --port 80 \
    --default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'

CloudFront тАФ certificate us-east-1 рдордзреНрдпреЗ рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ; distribution config рдордзреНрдпреЗ "ViewerProtocolPolicy": "redirect-to-https" рд▓рд╛рд╡рд╛ рдЖрдгрд┐:

"ViewerCertificate": {
  "ACMCertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9f8e7d6c-aaaa-bbbb-cccc-ddddeeeeffff",
  "SSLSupportMethod": "sni-only",
  "MinimumProtocolVersion": "TLSv1.2_2021"
}

Response headers policy рд╡рд╛рдкрд░реВрди CloudFront рдХрдбреВрди HSTS (рдХрд┐рдВрд╡рд╛ header app рдХрдбреВрди рдкрд╛рдард╡рд╛):

aws cloudfront create-response-headers-policy --response-headers-policy-config '{
  "Name": "school-security-headers",
  "SecurityHeadersConfig": {
    "StrictTransportSecurity": { "Override": true, "IncludeSubdomains": true, "Preload": false,
                                 "AccessControlMaxAgeSec": 31536000 },
    "ContentTypeOptions": { "Override": true },
    "FrameOptions": { "Override": true, "FrameOption": "DENY" }
  }
}'

Customer-managed KMS key рд╕рд╣ encryption at rest тАФ key (рджрд░рд╡рд░реНрд╖реА rotation рд╕рд╣), S3 default encryption, default рдиреЗ EBS encryption, рдЖрдгрд┐ рдПрдХ encrypted RDS database рдЬреЛ TLS рд╕реБрджреНрдзрд╛ рдмрдВрдзрдирдХрд╛рд░рдХ рдХрд░рддреЛ:

aws kms create-key --description "school data at rest" --query KeyMetadata.KeyId --output text
aws kms create-alias --alias-name alias/school-data --target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
aws kms enable-key-rotation --key-id 1234abcd-12ab-34cd-56ef-1234567890ab

aws s3api put-bucket-encryption --bucket school-results --server-side-encryption-configuration '{
  "Rules": [{ "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "aws:kms", "KMSMasterKeyID": "alias/school-data" },
              "BucketKeyEnabled": true }]
}'

aws ec2 enable-ebs-encryption-by-default --region ap-south-1
aws ec2 modify-ebs-default-kms-key-id --kms-key-id alias/school-data --region ap-south-1

aws rds create-db-instance --db-instance-identifier school-db --engine postgres \
    --db-instance-class db.t4g.medium --allocated-storage 50 \
    --master-username school_admin --manage-master-user-password \
    --storage-encrypted --kms-key-id alias/school-data \
    --db-parameter-group-name school-pg-tls     # a parameter group with rds.force_ssl = 1

(рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ unencrypted RDS instance рд╡рд░ encryption рдЬрд╛рдЧрдЪреНрдпрд╛ рдЬрд╛рдЧреА рдЪрд╛рд▓реВ рдХрд░рддрд╛ рдпреЗрдд рдирд╛рд╣реА: snapshot --kms-key-id рд╕рд╣ copy рдХрд░рд╛, рдордЧ encrypted copy рдордзреВрди restore рдХрд░рд╛.)

TLS рди рд╡рд╛рдкрд░рдгрд╛рд░реА рдХреЛрдгрддреАрд╣реА request рдирд╛рдХрд╛рд░рдгрд╛рд░реА bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyPlainHTTP",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": ["arn:aws:s3:::school-results", "arn:aws:s3:::school-results/*"],
    "Condition": { "Bool": { "aws:SecureTransport": "false" } }
  }]
}

рддреБрдордЪреНрдпрд╛ laptop рд╡рд░реВрди live endpoint рддрдкрд╛рд╕рд╛ тАФ рддрд╛рд░рдЦрд╛ рдЖрдгрд┐ рдирд╛рд╡реЗ, рдЖрдгрд┐ рдЬреБрдиреЗ TLS рдирд╛рдХрд╛рд░рд▓реЗ рдЬрд╛рддреЗ рддреЗ:

echo | openssl s_client -connect school.example:443 -servername school.example 2>/dev/null \
    | openssl x509 -noout -subject -enddate -ext subjectAltName
openssl s_client -connect school.example:443 -servername school.example -tls1_1 </dev/null   # should fail to connect

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: "ALB uses a modern TLS policy", "S3 bucket has default encryption", "EBS encryption by default" рдЖрдгрд┐ "RDS storage encrypted" рдпрд╛рдВрд╕рд╛рдареА AWS Config rules рдХрд┐рдВрд╡рд╛ Security Hub controls рдЪрд╛рд▓реВ рдХрд░рд╛, рдЖрдгрд┐ ACM certificates рдЪреНрдпрд╛ DaysToExpiry рд╡рд░ alarm рд▓рд╛рд╡рд╛. рдордЧ рдЪреБрдХреАрдЪреА setting рдЬреНрдпрд╛ рджрд┐рд╡рд╢реА рджрд┐рд╕рддреЗ рддреНрдпрд╛рдЪ рджрд┐рд╡рд╢реА robot рд▓рд╛ рд╕рд╛рдкрдбрддреЗ, рдЬреНрдпрд╛ рджрд┐рд╡рд╢реА рдХрд╛рд╣реАрддрд░реА рддреБрдЯрддреЗ рддреНрдпрд╛ рджрд┐рд╡рд╢реА рдкрд╛рд▓рдХрд╛рдВрдирд╛ рдирд╛рд╣реА.

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

рдЗрдорд╛рд░рдд рдЦрд┐рдбрдХреАрдЪреЗ рдХрд╛рдо рдкреВрд░реНрдг рдЭрд╛рд▓реЗ: secrets, permissions, networks рдЖрдгрд┐ encryption. рдкреБрдвреЗ рдЖрдкрдг рдкрд░рд┐рд╕рд░рд╛рдЪреНрдпрд╛ рджрд░рд╡рд╛рдЬрд╛рдВрдХрдбреЗ рдЬрд╛рддреЛ тАФ Kubernetes тАФ рдЖрдгрд┐ рддрд┐рдерд▓рд╛ рдкрд╣рд┐рд▓рд╛ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рддреЛ: cluster рдордзреНрдпреЗ рдХреЛрдг рдХрд╛рдп рдХрд░реВ рд╢рдХрддреЛ?

git checkout lesson-10-k8s-rbac

ЁЯФТ Lesson 09 тАФ Encryption in transit & at rest: sealed envelopes and locked cabinets

ЁЯУН You are here: Lesson 09 of 16 ┬╖ Previous: lesson-08-iam-network ┬╖ Next: lesson-10-k8s-rbac


ЁЯУж What's in this branch

Lessons 01тАУ08, plus the last cloud control: encryption. Data that moves (between a browser and the school website, between the app and the database) travels inside TLS. Data that sits (disks, buckets, database files, backups) is stored encrypted with a KMS key. You learn which TLS versions to allow, what a certificate proves, why it must match the name and must not expire, what HSTS adds, and how encryption at rest works with KMS.

ЁЯзТ Explain like I'm 5

The school sends report cards to parents. Two dangers:

  1. On the road. A letter can be opened by anyone who holds it on the way. So the office puts every letter in a sealed envelope ЁЯФТ that only the parent can open. That is encryption in transit (TLS).
  2. In the office. At night, someone could open the filing cabinet. So the cabinet is locked, and the key lives in a key safe ЁЯЧЭя╕П (lesson 07). That is encryption at rest (KMS).

And the courier who brings the envelope shows an ID badge. Dipika at the security office checks three things on the badge:

Old envelopes from the 1990s have weak seals. So the office also has a rule: "only the new envelopes тАФ TLS 1.2 or 1.3." And on the front door a sign says: "always use the sealed-envelope door, never the open one." That sign is HSTS.

ЁЯЧ║я╕П Diagram

flowchart LR
    p["ЁЯСк parent's browser"] -->|"ЁЯФТ TLS 1.2 / 1.3<br/>certificate: name тЬУ ┬╖ expiry тЬУ ┬╖ issuer тЬУ"| lb["тЪЦя╕П load balancer / CloudFront<br/>ACM certificate ┬╖ HSTS"]
    lb -->|"ЁЯФТ TLS again inside the VPC"| app["ЁЯН│ results API"]
    app -->|"ЁЯФТ TLS (rds.force_ssl)"| db["ЁЯУТ database<br/>ЁЯЧДя╕П encrypted at rest"]
    db --- k["ЁЯЧЭя╕П KMS key<br/>disks ┬╖ buckets ┬╖ backups"]
    bad["тЭМ api.school.example<br/>TLS 1.0 ┬╖ 6 days left ┬╖ wrong name"]

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

тЭУ What

ЁЯдФ Why

Because the network between two computers belongs to other people: Wi-Fi in a caf├й, an internet provider, a shared data centre. Without TLS, a password or a grade can be read or changed on the way. And a stolen disk, a public snapshot or a leaked backup is only a problem if the data inside it is readable. Encryption at rest with a key you control turns "someone copied the disk" into "someone copied noise". A wrong certificate name or an expired certificate is not a small warning either: it trains people to click "continue anyway", which is exactly what an attacker needs.

ЁЯФз How (in this repo)

tls_ok(cfg) in sec/cloud.py takes a configuration with host, cert_names, min_version and days_to_expiry, and returns (ok, problems). It flags:

tls() in sec/demo.py checks two sites: school.example (good) and api.school.example (three problems at once). The model compares names exactly тАФ it does not know wildcard rules. A real browser would accept *.school.example for results.school.example. You will see that in the snippet.

ЁЯзк Try it

python3 sec/demo.py tls
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import tls_ok
base = dict(host="results.school.example", cert_names=["results.school.example"], min_version=1.2, days_to_expiry=60)
for label, change in (("as set up", {}), ("TLS 1.3 only", dict(min_version=1.3)), ("TLS 1.1 allowed", dict(min_version=1.1)),
                      ("expires in 14 days", dict(days_to_expiry=14)), ("expires in 13 days", dict(days_to_expiry=13)),
                      ("wildcard certificate", dict(cert_names=["*.school.example"]))):
    ok, problems = tls_ok({**base, **change})
    print(f"{label:<22} тЖТ {'ok' if ok else problems}")
EOF
python3 sec/test_sec.py

тЬЕ Verify тАФ what you should see

tls prints:

тХРтХРтХР tls тХРтХРтХР
тФАтФА school.example       тЖТ ok
тФАтФА api.school.example   тЖТ TLS 1.0 allowed тАФ require 1.2+; certificate expires in 6 days; certificate name does not match the host
   in transit: TLS 1.2+ everywhere, HSTS ┬╖ at rest: encrypt disks, buckets, backups with KMS keys ┬╖ rotate certificates automatically

тЬЕ done тАФ every door checked

Your snippet prints:

as set up              тЖТ ok
TLS 1.3 only           тЖТ ok
TLS 1.1 allowed        тЖТ ['TLS 1.1 allowed тАФ require 1.2+']
expires in 14 days     тЖТ ok
expires in 13 days     тЖТ ['certificate expires in 13 days']
wildcard certificate   тЖТ ['certificate name does not match the host']

The last line is the model being stricter than a browser: a real client accepts *.school.example for results.school.example. The tests include тЬЕ L09 old TLS and near-expiry certificates are flagged and end with 16/16 passed.

ЁЯПБ What you just proved

One site can have three separate TLS problems, and each one is enough to break trust: an old protocol, a certificate about to expire, and a name that does not match. The check is small and fast, so it belongs in a scheduled job that looks at every public endpoint, not in someone's memory. Allowing only 1.3 is fine for the model; in real life keep 1.2 as well until you know every client supports 1.3.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ a certificate from ACM, validated through DNS (ACM renews it automatically while it is in use):

aws acm request-certificate --domain-name school.example \
    --subject-alternative-names www.school.example api.school.example \
    --validation-method DNS
aws acm describe-certificate --certificate-arn arn:aws:acm:ap-south-1:111122223333:certificate/0a1b2c3d-1111-2222-3333-444455556666 \
    --query 'Certificate.{status:Status,names:SubjectAlternativeNames,notAfter:NotAfter,renewal:RenewalEligibility}'

An ALB listener that allows only TLS 1.3 and 1.2, and a port-80 listener that only redirects:

aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:ap-south-1:111122223333:loadbalancer/app/school-alb/50dc6c495c0c9188 \
    --protocol HTTPS --port 443 \
    --certificates CertificateArn=arn:aws:acm:ap-south-1:111122223333:certificate/0a1b2c3d-1111-2222-3333-444455556666 \
    --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
    --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:ap-south-1:111122223333:targetgroup/results-api/6d0ecf831eec9f09
aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:ap-south-1:111122223333:loadbalancer/app/school-alb/50dc6c495c0c9188 \
    --protocol HTTP --port 80 \
    --default-actions 'Type=redirect,RedirectConfig={Protocol=HTTPS,Port=443,StatusCode=HTTP_301}'

CloudFront тАФ the certificate must be in us-east-1; in the distribution config set "ViewerProtocolPolicy": "redirect-to-https" and:

"ViewerCertificate": {
  "ACMCertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9f8e7d6c-aaaa-bbbb-cccc-ddddeeeeffff",
  "SSLSupportMethod": "sni-only",
  "MinimumProtocolVersion": "TLSv1.2_2021"
}

HSTS from CloudFront with a response headers policy (or send the header from the app):

aws cloudfront create-response-headers-policy --response-headers-policy-config '{
  "Name": "school-security-headers",
  "SecurityHeadersConfig": {
    "StrictTransportSecurity": { "Override": true, "IncludeSubdomains": true, "Preload": false,
                                 "AccessControlMaxAgeSec": 31536000 },
    "ContentTypeOptions": { "Override": true },
    "FrameOptions": { "Override": true, "FrameOption": "DENY" }
  }
}'

Encryption at rest with a customer-managed KMS key тАФ the key (with yearly rotation), S3 default encryption, EBS encryption by default, and an encrypted RDS database that also requires TLS:

aws kms create-key --description "school data at rest" --query KeyMetadata.KeyId --output text
aws kms create-alias --alias-name alias/school-data --target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
aws kms enable-key-rotation --key-id 1234abcd-12ab-34cd-56ef-1234567890ab

aws s3api put-bucket-encryption --bucket school-results --server-side-encryption-configuration '{
  "Rules": [{ "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "aws:kms", "KMSMasterKeyID": "alias/school-data" },
              "BucketKeyEnabled": true }]
}'

aws ec2 enable-ebs-encryption-by-default --region ap-south-1
aws ec2 modify-ebs-default-kms-key-id --kms-key-id alias/school-data --region ap-south-1

aws rds create-db-instance --db-instance-identifier school-db --engine postgres \
    --db-instance-class db.t4g.medium --allocated-storage 50 \
    --master-username school_admin --manage-master-user-password \
    --storage-encrypted --kms-key-id alias/school-data \
    --db-parameter-group-name school-pg-tls     # a parameter group with rds.force_ssl = 1

(An existing unencrypted RDS instance cannot be switched on in place: copy a snapshot with --kms-key-id, then restore from the encrypted copy.)

A bucket policy that refuses any request that does not use TLS:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenyPlainHTTP",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": ["arn:aws:s3:::school-results", "arn:aws:s3:::school-results/*"],
    "Condition": { "Bool": { "aws:SecureTransport": "false" } }
  }]
}

Check a live endpoint from your laptop тАФ the dates and names, and that old TLS is refused:

echo | openssl s_client -connect school.example:443 -servername school.example 2>/dev/null \
    | openssl x509 -noout -subject -enddate -ext subjectAltName
openssl s_client -connect school.example:443 -servername school.example -tls1_1 </dev/null   # should fail to connect

ЁЯПн Why this matters in production: turn on AWS Config rules or Security Hub controls for "ALB uses a modern TLS policy", "S3 bucket has default encryption", "EBS encryption by default" and "RDS storage encrypted", and add an alarm on DaysToExpiry for ACM certificates. Then a bad setting is found by a robot the day it appears, not by a parent the day it breaks.

тПня╕П Next

The building desk is done: secrets, permissions, networks and encryption. Next we walk to the campus gates тАФ Kubernetes тАФ and ask the first question there: who may do what in the cluster?

git checkout lesson-10-k8s-rbac
тЖР Previousiam networkNext тЖТk8s rbac

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