ЁЯПл The SchoolтА║ЁЯУР System DesignтА║ЁЯФР рдзрдбрд╛ 14 тАФ Design рдордзреВрдирдЪ security: рдкреНрд░рддреНрдпреЗрдХ box рд╕рд╛рдареА рд╕рд╣рд╛ рдкреНрд░рд╢реНрди
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФР рдзрдбрд╛ 14 тАФ Design рдордзреВрдирдЪ security: рдкреНрд░рддреНрдпреЗрдХ box рд╕рд╛рдареА рд╕рд╣рд╛ рдкреНрд░рд╢реНрди

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 14 ┬╖ рдкреБрдвреАрд▓: lesson-15-observability


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

рдзрдбреЗ 01тАУ13, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ box рд╕рд╛рдареА рджреБрд╕рд░рд╛ рдкреНрд░рд╢реНрди: рдХреЛрдгреА рддреНрдпрд╛рдЪрд╛ рдЧреИрд░рд╡рд╛рдкрд░ рдХрд╕рд╛ рдХрд░реВ рд╢рдХреЗрд▓? рддреБрдореНрд╣реА рд╢рд┐рдХрддрд╛ рдкреНрд░рддреНрдпреЗрдХ box рд▓рд╛ рд╡рд┐рдЪрд╛рд░рд▓реЗрд▓рд╛ STRIDE, authentication vs authorization, рдкреНрд░рддреНрдпреЗрдХ object рд╕рд╛рдареА authorization, transit рдЖрдгрд┐ at rest рдордзреАрд▓ encryption, secrets, least privilege, рдЖрдгрд┐ abuse cases. Identity рдЖрдгрд┐ permission рдЪреНрдпрд╛ рддрдкрд╢рд┐рд▓рд╛рдВрд╕рд╛рдареА IAM рд╢рд╛рд│рд╛ рдкрд╛рд╣рд╛.

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

рджреАрдкрд┐рдХрд╛ рдкреБрдиреНрд╣рд╛ рдЖрдХреГрддреА рдШреЗрддреЗ. рдпрд╛ рд╡реЗрд│реА рддреА рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рд╕рд╣рд╛ рдкреНрд░рд╢реНрдирд╛рдВрдЪреЗ рдПрдХ рдХрд╛рд░реНрдб рджреЗрддреЗ. рдРрд╢реНрд╡рд░реНрдпрд╛рдиреЗ рднрд┐рдВрддреАрд╡рд░реАрд▓ рдкреНрд░рддреНрдпреЗрдХ box рд▓рд╛ рд╕рд╣рд╛рд╣реА рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рд╛рдпрдЪреЗ рдЖрд╣реЗрдд.

  1. ЁЯОн рдЦрд░реЛрдЦрд░ рдХреЛрдг рд╡рд┐рдЪрд╛рд░рдд рдЖрд╣реЗ? рдХреЛрдгреА рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЕрд╕рд▓реНрдпрд╛рдЪреЗ рд╕реЛрдВрдЧ рдШреЗрдК рд╢рдХреЗрд▓ рдХрд╛?
  2. тЬПя╕П рдХреЛрдгреА рддреЗ рдмрджрд▓реВ рд╢рдХреЗрд▓ рдХрд╛? рд╡рд╛рдЯреЗрдд, рдХрд┐рдВрд╡рд╛ рдиреЛрдВрджрд╡рд╣реАрдд?
  3. ЁЯЩИ рдХреЛрдгреА "рдореА рддреЗ рдХреЗрд▓реЗрдЪ рдирд╛рд╣реА" рдЕрд╕реЗ рдореНрд╣рдгреВ рд╢рдХреЗрд▓ рдХрд╛? рддреНрдпрд╛рдВрдирд╛ рдкреБрд╕рддрд╛ рди рдпреЗрдгрд╛рд░реА рдиреЛрдВрдж рдЖрд╣реЗ рдХрд╛?
  4. ЁЯСА рддреЗ рдХреЛрдг рд╡рд╛рдЪреВ рд╢рдХрддреЗ? рдПрдЦрд╛рджрд╛ рдкрд╛рд▓рдХ рджреБрд╕рд▒реНрдпрд╛ рд╡рд░реНрдЧрд╛рдЪреНрдпрд╛ рд╕реВрдЪрдирд╛ рд╡рд╛рдЪреВ рд╢рдХреЗрд▓ рдХрд╛?
  5. ЁЯМК рдХреЛрдгреА рддреНрдпрд╛рд╡рд░ рд▓реЛрдВрдврд╛ рдЖрдгреВ рд╢рдХреЗрд▓ рдХрд╛? (рдзрдбрд╛ 12 рдордзреАрд▓ token рдЪреА рдмрд░рдгреА.)
  6. ЁЯСС рдХреЛрдгреА рдореБрдЦреНрдпрд╛рдзреНрдпрд╛рдкрд┐рдХрд╛ рдмрдиреВ рд╢рдХреЗрд▓ рдХрд╛? рдПрдЦрд╛рджреНрдпрд╛ рдкрд╛рд▓рдХрд╛рд▓рд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪреЗ рдЕрдзрд┐рдХрд╛рд░ рдорд┐рд│реВ рд╢рдХрддреАрд▓ рдХрд╛?

рдордЧ рдХрддрд░рд┐рдирд╛ рдЦрд▓рдирд╛рдпрд┐рдХреЗрдЪреА рднреВрдорд┐рдХрд╛ рдХрд░рддреЗ. "рдореА рдПрдХрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪрд╛ password рдЪреЛрд░рддреЗ. рдореА рдкреНрд░рддреНрдпреЗрдХ рд╢рд╛рд│реЗрд▓рд╛ рдПрдХ рддрд╛рддрдбреАрдЪреА рд╕реВрдЪрдирд╛ post рдХрд░рддреЗ: рдЙрджреНрдпрд╛ рд╢рд╛рд│рд╛ рдмрдВрдж. рдкрдиреНрдирд╛рд╕ рд▓рд╛рдЦ SMS рдЬрд╛рддрд╛рдд." рдЦреЛрд▓реА рд╢рд╛рдВрдд рд╣реЛрддреЗ. рдореНрд╣рдгреВрди рдпреЛрдЬрдиреЗрдд рдирд╡реНрдпрд╛ рдУрд│реА рдпреЗрддрд╛рдд: рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рджреБрд╕рд▒реНрдпрд╛ factor рд╕рд╣ login рдХрд░рддрд╛рдд; рдПрдХрд╛рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рд╢рд╛рд│рд╛рдВрдирд╛ рдЬрд╛рдгрд╛рд▒реНрдпрд╛ рддрд╛рддрдбреАрдЪреНрдпрд╛ рд╕реВрдЪрдиреЗрд▓рд╛ рдордВрдЬреБрд░реАрд╕рд╛рдареА рджреБрд╕рд░реА рд╡реНрдпрдХреНрддреА рд▓рд╛рдЧрддреЗ; рддрд╛рддрдбреАрдЪреНрдпрд╛ рд╕реВрдЪрдирд╛рдВрдирд╛ рджрд┐рд╡рд╕рд╛рдЪреА рдорд░реНрдпрд╛рджрд╛ рдЕрд╕рддреЗ; рдкреНрд░рддреНрдпреЗрдХ post рдХреЛрдгрд╛рд▓рд╛рдЪ edit рдХрд░рддрд╛ рди рдпреЗрдгрд╛рд▒реНрдпрд╛ log рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реА рдЬрд╛рддреЗ.

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

flowchart LR
    p["ЁЯУ▒ parent app"] -->|"TLS"| gw["ЁЯЪк API gateway<br/>authn: who are you?"]
    gw --> api["ЁЯзй notice API<br/>authz per object:<br/>is 3A your class?"]
    api -->|"TLS"| db[("ЁЯЧДя╕П notices DB<br/>encrypted at rest")]
    api --> sec["ЁЯФТ secrets vault"]
    api --> q["ЁЯУм SMS queue"] --> w["ЁЯУд SMS worker<br/>least privilege"]
    api --> log["ЁЯУЬ audit log<br/>append-only"]
    st["ЁЯГП STRIDE per box:<br/>Spoofing ┬╖ Tampering ┬╖ Repudiation<br/>Info disclosure ┬╖ DoS ┬╖ Elevation"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг design рдирдВрддрд░ рдЬреЛрдбрд▓реЗрд▓реА security рдореНрд╣рдгрдЬреЗ рдард┐рдЧрд│рд╛рдВрдЪрд╛ рд╕рдВрдЪ, рдЖрдгрд┐ design рдордзреНрдпреЗрдЪ рдЕрд╕рд▓реЗрд▓реА security рдореНрд╣рдгрдЬреЗ boxes рдЖрдгрд┐ рдирд┐рдпрдорд╛рдВрдЪрд╛ рд╕рдВрдЪ рдЬреНрдпрд╛рдиреБрд╕рд╛рд░ рд╕рдЧрд│реЗ рдмрд╛рдВрдзрддрд╛рдд. рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рдд рдореБрд▓рд╛рдВрдЪреА рдирд╛рд╡реЗ рдЖрдгрд┐ рдкрд╛рд▓рдХрд╛рдВрдЪреЗ рдлреЛрди рдирдВрдмрд░ рдЕрд╕рддрд╛рдд, рдЖрдгрд┐ рддреЛ рд▓рд╛рдЦреЛ SMS рдкрд╛рдард╡реВ рд╢рдХрддреЛ. рдпрд╛ рджреЛрди рдЧреЛрд╖реНрдЯреАрдВрдореБрд│реЗ рддреНрдпрд╛рд╡рд░ рд╣рд▓реНрд▓рд╛ рдХрд░рдгреЗ рдлрд╛рдпрджреНрдпрд╛рдЪреЗ рдард░рддреЗ. рдпреЛрдЬрдиреЗрд╡рд░ рдкреНрд░рддреНрдпреЗрдХ box рд▓рд╛ рд╕рд╣рд╛ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рд╛рдпрд▓рд╛ рдПрдХ рддрд╛рд╕ рд▓рд╛рдЧрддреЛ; leak рдирдВрддрд░ рдЙрддреНрддрд░ рд╢реЛрдзрд╛рдпрд▓рд╛ рдорд╣рд┐рдиреЗ рд▓рд╛рдЧрддрд╛рдд.

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

design/designs.py рдордзреАрд▓ STRIDE рдкреНрд░рддреНрдпреЗрдХ рдзреЛрдХреНрдпрд╛рд▓рд╛ рддреНрдпрд╛рдЪрд╛ рдкреНрд░рд╢реНрди рдЖрдгрд┐ рдиреЗрд╣рдореАрдЪреЗ controls рдЬреЛрдбрддреЛ. stride(component) рдПрдХрд╛ box рд╕рд╛рдареА рд╕рд╣рд╛ (threat, question) rows рдкрд░рдд рдХрд░рддреЛ. design/demo.py рдордзреАрд▓ security() рддреЗ notice API рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЛ. Snippet рд╕рд╣рд╛ boxes рд╕рд╛рдареА рдкреВрд░реНрдг рдХрд╛рдЧрдж рддрдпрд╛рд░ рдХрд░рддреЛ рдЖрдгрд┐ inline рд▓рд┐рд╣рд┐рд▓реЗрд▓реА рдПрдХ рдЫреЛрдЯреА рдкреНрд░рддрд┐-object authorization рддрдкрд╛рд╕рдгреА рдЬреЛрдбрддреЛ.

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

python3 design/demo.py security
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from designs import stride
boxes = ["mobile app", "API gateway", "notice API", "notices DB", "SMS queue", "SMS worker"]
sheet = [row for b in boxes for row in stride(b)]
print(len(boxes), "boxes ├Ч", len(stride("x")), "threats тЖТ", len(sheet), "questions on the sheet")
for threat, q in stride("SMS worker")[3:5]: print(f"{threat:<24} {q}")
parent_classes = {"aishwarya": {"3A"}, "katrina": {"5B"}}
def can_read(user, class_id): return class_id in parent_classes.get(user, set())
for user, cls in (("aishwarya", "3A"), ("aishwarya", "5B"), ("stranger", "3A")):
    print(f"GET /classes/{cls}/notices as {user:<9} тЖТ {'200' if can_read(user, cls) else '403' if user in parent_classes else '401'}")
EOF

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

security рд╣реЗ рдЫрд╛рдкрддреЛ:

тФАтФА STRIDE, asked of every box on the diagram (here: the notice API)
   Spoofing                 notice API: who is really calling? тЖТ authentication (OIDC, mTLS, signed requests)
   Tampering                notice API: was it changed on the way or at rest? тЖТ TLS, signatures, checksums, least-privilege writes
   Repudiation              notice API: can someone deny doing it? тЖТ audit logs they cannot edit (CloudTrail, append-only)
   Information disclosure   notice API: who can read it? тЖТ encryption, authorization per object, no secrets in logs
   Denial of service        notice API: can it be flooded? тЖТ rate limits, WAF, quotas, autoscaling with a ceiling
   Elevation of privilege   notice API: can a user become an admin? тЖТ least privilege, input validation, isolation
   plus: parents see only their children's classes (authorization per object), PII encrypted, secrets in a vault

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

6 boxes ├Ч 6 threats тЖТ 36 questions on the sheet
Information disclosure   SMS worker: who can read it? тЖТ encryption, authorization per object, no secrets in logs
Denial of service        SMS worker: can it be flooded? тЖТ rate limits, WAF, quotas, autoscaling with a ceiling
GET /classes/3A/notices as aishwarya тЖТ 200
GET /classes/5B/notices as aishwarya тЖТ 403
GET /classes/3A/notices as stranger  тЖТ 401

Tests рдордзреНрдпреЗ тЬЕ L14 STRIDE covers six threats рдЕрд╕рддреЗ.

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

рд╕рд╣рд╛ boxes рдЪреНрдпрд╛ рдЫреЛрдЯреНрдпрд╛ design рдордзреНрдпреЗрдЪ security рдЪреЗ 36 рдкреНрд░рд╢реНрди рдЖрд╣реЗрдд тАФ рдореНрд╣рдгреВрдирдЪ рдХрд╛рд░реНрдпрд╛рд▓рдп рдЖрдард╡рдгреАрд╡рд░ рдирд╛рд╣реА рддрд░ checklist рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рд░рд╛рд╣рддреЗ. рддреЗрдЪ рд╕рд╣рд╛ рдкреНрд░рд╢реНрди рдкреНрд░рддреНрдпреЗрдХ box рд╕рд╛рдареА рд╡реЗрдЧрд╡реЗрдЧрд│реА рдЙрддреНрддрд░реЗ рджреЗрддрд╛рдд: SMS worker рд╕рд╛рдареА "information disclosure" рдореНрд╣рдгрдЬреЗ рддреНрдпрд╛рдЪреНрдпрд╛ logs рдордзреАрд▓ рдлреЛрди рдирдВрдмрд░, рдЖрдгрд┐ "denial of service" рдореНрд╣рдгрдЬреЗ рдХреЛрдгреАрддрд░реА рддреНрдпрд╛рдЪреА queue рднрд░реВрди рдЯрд╛рдХрдгреЗ. рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ object рд╕рд╛рдареА authorization рдПрдХрд╛рдЪ URL рд▓рд╛ рддреАрди рд╡реЗрдЧрд╡реЗрдЧрд│реА рдЙрддреНрддрд░реЗ рджреЗрддреЗ: рдРрд╢реНрд╡рд░реНрдпрд╛рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ рд╡рд░реНрдЧрд╛рд╕рд╛рдареА 200, рддрд┐рдЪрд╛ рдирд╕рд▓реЗрд▓реНрдпрд╛ рд╡рд░реНрдЧрд╛рд╕рд╛рдареА 403, login рди рдХреЗрд▓реЗрд▓реНрдпрд╛ рд╡реНрдпрдХреНрддреАрд╕рд╛рдареА 401.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ PostgreSQL row-level security рд╡рд╛рдкрд░реВрди database рдордзреНрдпреЗрдЪ рдкреНрд░рддреНрдпреЗрдХ object рд╕рд╛рдареА authorization. WHERE рд╡рд┐рд╕рд░рд▓реЗрд▓реА query рд╕реБрджреНрдзрд╛ рдлрдХреНрдд рддреНрдпрд╛ рдкрд╛рд▓рдХрд╛рдЪреЗрдЪ рд╡рд░реНрдЧ рдкрд░рдд рдХрд░рддреЗ:

ALTER TABLE notices ENABLE ROW LEVEL SECURITY;
CREATE POLICY parent_reads_own_classes ON notices FOR SELECT
  USING (class_id IN (SELECT class_id FROM parent_classes
                      WHERE parent_id = current_setting('app.parent_id')::bigint));
-- the API sets the caller for each transaction:
SET LOCAL app.parent_id = '42';

SMS worker рд╕рд╛рдареА least privilege тАФ рддреЛ рдПрдХрд╛рдЪ queue рдордзреВрди messages рдШреЗрдК рд╢рдХрддреЛ, рдмрд╛рдХреА рдХрд╛рд╣реАрдЪ рдирд╛рд╣реА:

{ "Version": "2012-10-17",
  "Statement": [{ "Effect": "Allow",
    "Action": ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"],
    "Resource": "arn:aws:sqs:ap-south-1:111122223333:urgent-sms" }] }

Secrets start-up рд╡реЗрд│реА vault рдордзреВрди, code рдордзреВрди рдХрдзреАрдЪ рдирд╛рд╣реА:

aws secretsmanager get-secret-value --secret-id notice-board/db --query SecretString --output text
aws secretsmanager rotate-secret --secret-id notice-board/db --rotation-rules AutomaticallyAfterDays=30

Database рдЖрдгрд┐ files bucket рд╕рд╛рдареА at rest encryption:

aws rds create-db-instance --db-instance-identifier notices-db --engine postgres \
    --db-instance-class db.m7g.large --allocated-storage 100 \
    --master-username app --manage-master-user-password \
    --storage-encrypted --kms-key-id alias/notice-board
aws s3api put-bucket-encryption --bucket notice-board-files \
    --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms","KMSMasterKeyID":"alias/notice-board"}}]}'

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: STRIDE рдХрд╛рдЧрдж repo рдордзреНрдпреЗ design рд╢реЗрдЬрд╛рд░реА рдареЗрд╡рд╛, рдкреНрд░рддреНрдпреЗрдХ box рд╕рд╛рдареА рдПрдХ row, рдЖрдгрд┐ рдирд╡рд╛ box рдЬреЛрдбрд▓рд╛ рдХреА рддреНрдпрд╛рдЪрд╛ рдЖрдврд╛рд╡рд╛ рдШреНрдпрд╛. Roles, policies, federation рдЖрдгрд┐ permission boundaries рд╕рд╛рдареА рдкреБрдвреЗ IAM рд╢рд╛рд│рд╛ рдордзреНрдпреЗ рдЪрд▓рд╛.

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

рдХрд╛рдп рдЕрдкрдпрд╢реА рд╣реЛрддреЗ рдЖрдгрд┐ рдХрд╢рд╛рд╡рд░ рд╣рд▓реНрд▓рд╛ рд╣реЛрддреЛ рд╣реЗ рдЖрддрд╛ рдпреЛрдЬрдирд╛ рд╕рд╛рдВрдЧрддреЗ. рдкрдг launch рдирдВрддрд░ app рдирд┐рд░реЛрдЧреА рдЖрд╣реЗ рд╣реЗ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рд▓рд╛ рдХрд╕реЗ рдХрд│реЗрд▓? Design рдордзреВрдирдЪ observability.

git checkout lesson-15-observability

ЁЯФР Lesson 14 тАФ Security by design: six questions for every box

ЁЯУН You are here: Lesson 14 of 18 ┬╖ Next: lesson-15-observability


ЁЯУж What's in this branch

Lessons 01тАУ13, plus the second question for every box: how could someone misuse it? You learn STRIDE asked per box, authentication vs authorization, authorization per object, encryption in transit and at rest, secrets, least privilege, and abuse cases. For the identity and permission details, go to the IAM school.

ЁЯзТ Explain like I'm 5

Dipika takes the drawing again. This time she gives Aishwarya a card with six questions. Aishwarya must ask all six of every box on the wall.

  1. ЁЯОн Who is really asking? Could someone pretend to be a teacher?
  2. тЬПя╕П Could someone change it? On the way, or in the book?
  3. ЁЯЩИ Could someone say "I never did that"? Is there a record they cannot erase?
  4. ЁЯСА Who can read it? Could a parent read another class's notices?
  5. ЁЯМК Could someone flood it? (Lesson 12's token jar.)
  6. ЁЯСС Could someone become the head teacher? Could a parent get teacher powers?

Then Katrina plays the bad actor. "I steal a teacher's password. I post an urgent notice to every school: school closed tomorrow. Five million SMS go out." The room is quiet. So the plan gets new lines: teachers log in with a second factor; an urgent notice to more than one school needs a second person to approve it; urgent notices have a limit per day; every post is written in a log nobody can edit.

ЁЯЧ║я╕П Diagram

flowchart LR
    p["ЁЯУ▒ parent app"] -->|"TLS"| gw["ЁЯЪк API gateway<br/>authn: who are you?"]
    gw --> api["ЁЯзй notice API<br/>authz per object:<br/>is 3A your class?"]
    api -->|"TLS"| db[("ЁЯЧДя╕П notices DB<br/>encrypted at rest")]
    api --> sec["ЁЯФТ secrets vault"]
    api --> q["ЁЯУм SMS queue"] --> w["ЁЯУд SMS worker<br/>least privilege"]
    api --> log["ЁЯУЬ audit log<br/>append-only"]
    st["ЁЯГП STRIDE per box:<br/>Spoofing ┬╖ Tampering ┬╖ Repudiation<br/>Info disclosure ┬╖ DoS ┬╖ Elevation"]

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

тЭУ What

ЁЯдФ Why

Because security added after the design is a set of patches, and security in the design is a set of boxes and rules everybody builds to. The notice board holds children's names and parents' phone numbers, and it can send millions of SMS. Those two facts make it worth attacking. Asking six questions per box takes an hour on the plan; finding the answer after a leak takes months.

ЁЯФз How (in this repo)

STRIDE in design/designs.py maps each threat to its question and typical controls. stride(component) returns six (threat, question) rows for one box. security() in design/demo.py asks them of the notice API. The snippet builds the whole sheet for six boxes and adds a tiny per-object authorization check, written inline.

ЁЯзк Try it

python3 design/demo.py security
python3 - <<'EOF'
import sys; sys.path.insert(0, "design"); from designs import stride
boxes = ["mobile app", "API gateway", "notice API", "notices DB", "SMS queue", "SMS worker"]
sheet = [row for b in boxes for row in stride(b)]
print(len(boxes), "boxes ├Ч", len(stride("x")), "threats тЖТ", len(sheet), "questions on the sheet")
for threat, q in stride("SMS worker")[3:5]: print(f"{threat:<24} {q}")
parent_classes = {"aishwarya": {"3A"}, "katrina": {"5B"}}
def can_read(user, class_id): return class_id in parent_classes.get(user, set())
for user, cls in (("aishwarya", "3A"), ("aishwarya", "5B"), ("stranger", "3A")):
    print(f"GET /classes/{cls}/notices as {user:<9} тЖТ {'200' if can_read(user, cls) else '403' if user in parent_classes else '401'}")
EOF

тЬЕ Verify тАФ what you should see

security prints:

тФАтФА STRIDE, asked of every box on the diagram (here: the notice API)
   Spoofing                 notice API: who is really calling? тЖТ authentication (OIDC, mTLS, signed requests)
   Tampering                notice API: was it changed on the way or at rest? тЖТ TLS, signatures, checksums, least-privilege writes
   Repudiation              notice API: can someone deny doing it? тЖТ audit logs they cannot edit (CloudTrail, append-only)
   Information disclosure   notice API: who can read it? тЖТ encryption, authorization per object, no secrets in logs
   Denial of service        notice API: can it be flooded? тЖТ rate limits, WAF, quotas, autoscaling with a ceiling
   Elevation of privilege   notice API: can a user become an admin? тЖТ least privilege, input validation, isolation
   plus: parents see only their children's classes (authorization per object), PII encrypted, secrets in a vault

Your snippet prints:

6 boxes ├Ч 6 threats тЖТ 36 questions on the sheet
Information disclosure   SMS worker: who can read it? тЖТ encryption, authorization per object, no secrets in logs
Denial of service        SMS worker: can it be flooded? тЖТ rate limits, WAF, quotas, autoscaling with a ceiling
GET /classes/3A/notices as aishwarya тЖТ 200
GET /classes/5B/notices as aishwarya тЖТ 403
GET /classes/3A/notices as stranger  тЖТ 401

The tests include тЬЕ L14 STRIDE covers six threats.

ЁЯПБ What you just proved

A small design with six boxes already has 36 security questions тАФ that is why the office uses a checklist and not memory. The same six questions give different answers per box: for the SMS worker, "information disclosure" means phone numbers in its logs, and "denial of service" means someone filling its queue. And authorization per object gives three different answers for one URL: 200 for Aishwarya's own class, 403 for a class that is not hers, 401 for someone who has not logged in.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ authorization per object in the database with PostgreSQL row-level security. Even a query with a forgotten WHERE returns only the parent's classes:

ALTER TABLE notices ENABLE ROW LEVEL SECURITY;
CREATE POLICY parent_reads_own_classes ON notices FOR SELECT
  USING (class_id IN (SELECT class_id FROM parent_classes
                      WHERE parent_id = current_setting('app.parent_id')::bigint));
-- the API sets the caller for each transaction:
SET LOCAL app.parent_id = '42';

Least privilege for the SMS worker тАФ it may take messages from one queue, nothing else:

{ "Version": "2012-10-17",
  "Statement": [{ "Effect": "Allow",
    "Action": ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"],
    "Resource": "arn:aws:sqs:ap-south-1:111122223333:urgent-sms" }] }

Secrets from a vault at start-up, never from code:

aws secretsmanager get-secret-value --secret-id notice-board/db --query SecretString --output text
aws secretsmanager rotate-secret --secret-id notice-board/db --rotation-rules AutomaticallyAfterDays=30

Encryption at rest for the database and the files bucket:

aws rds create-db-instance --db-instance-identifier notices-db --engine postgres \
    --db-instance-class db.m7g.large --allocated-storage 100 \
    --master-username app --manage-master-user-password \
    --storage-encrypted --kms-key-id alias/notice-board
aws s3api put-bucket-encryption --bucket notice-board-files \
    --server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms","KMSMasterKeyID":"alias/notice-board"}}]}'

ЁЯПн Why this matters in production: keep the STRIDE sheet in the repo next to the design, one row per box, and review it when a box is added. For roles, policies, federation and permission boundaries, continue in the IAM school.

тПня╕П Next

The plan now says what fails and what is attacked. But how will the office know that the app is healthy after launch? Observability by design.

git checkout lesson-15-observability
тЖР PreviousresilienceNext тЖТobservability

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