ЁЯПл The SchoolтА║ЁЯЫбя╕П SecurityтА║ЁЯЧЭя╕П рдзрдбрд╛ 07 тАФ Secrets рдЖрдгрд┐ KMS: keys рддрд┐рдЬреЛрд░реАрдд, code рдордзреНрдпреЗ рдирд╛рд╣реА
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЧЭя╕П рдзрдбрд╛ 07 тАФ Secrets рдЖрдгрд┐ KMS: keys рддрд┐рдЬреЛрд░реАрдд, code рдордзреНрдпреЗ рдирд╛рд╣реА

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 16 рдкреИрдХреА рдзрдбрд╛ 07 ┬╖ рдорд╛рдЧреЗ: lesson-06-authorization ┬╖ рдкреБрдвреЗ: lesson-08-iam-network


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

рдзрдбреЗ 01тАУ06, рдЖрдгрд┐ рдЗрдорд╛рд░рдд рдЦрд┐рдбрдХреАрдЪрд╛ рдкрд╣рд┐рд▓рд╛ рдзрдбрд╛. рджреЛрди рдХрд╛рдореЗ. рдкрд╣рд┐рд▓реЗ, envelope encryption: рдПрдХ рддрд╛рдЬреА data key data encrypt рдХрд░рддреЗ, рдЖрдгрд┐ KMS рдХрдзреАрдЪ рди рд╕реЛрдбрдгрд╛рд░реА master key рдлрдХреНрдд data key encrypt рдХрд░рддреЗ. рджреБрд╕рд░реЗ, рдЧреБрдкрд┐рддреЗ (secrets) тАФ database passwords, API keys тАФ files рдордзреНрдпреЗ рдирд╛рд╣реА, рддрд░ secret manager рдордзреНрдпреЗ рд░рд╛рд╣рддрд╛рдд, рдЖрдгрд┐ secret scanner code рдордзреНрдпреЗ рдирд┐рд╕рдЯреВрди рдЖрд▓реЗрд▓реА рдЧреБрдкрд┐рддреЗ рд╢реЛрдзрддреЛ. sec/demo.py рдордзрд▓реЗ secrets() рджреЛрдиреНрд╣реА рджрд╛рдЦрд╡рддреЗ.

тЪая╕П sec/cloud.py рдордзрд▓рд╛ cipher рд╣рд╛ SHA-256 рдкрд╛рд╕реВрди рдмрдирд╡рд▓реЗрд▓рд╛ рд╢рд┐рдХрд╡рдгреНрдпрд╛рд╕рд╛рдареАрдЪрд╛ keystream рдЖрд╣реЗ. рддреЛ рдлрдХреНрдд envelope encryption рдЪрд╛ рдЖрдХрд╛рд░ рджрд╛рдЦрд╡рддреЛ. рддреНрдпрд╛рдд integrity check рдирд╛рд╣реА рдЖрдгрд┐ рддреНрдпрд╛рдиреЗ рдХрдзреАрд╣реА рдЦрд▒реНрдпрд╛ data рдЪреЗ рд╕рдВрд░рдХреНрд╖рдг рдХрд░реВ рдирдпреЗ. рдЦрд▒реНрдпрд╛ systems AWS KMS рдЖрдгрд┐ рддрдкрд╛рд╕рд▓реЗрд▓реА library (AES-256-GCM) рд╡рд╛рдкрд░рддрд╛рдд. рд╕реНрд╡рддрдГрдЪреА cryptography рдХрдзреАрд╣реА рд▓рд┐рд╣реВ рдирдХрд╛.

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

рд╢рд╛рд│рд╛ рдкреНрд░рдЧрддреАрдкреБрд╕реНрддрдХреЗ рдПрдХрд╛ рдХрдкрд╛рдЯрд╛рдд рдареЗрд╡рддреЗ. рдкреНрд░рддреНрдпреЗрдХ рдХрдкрд╛рдЯрд╛рдЪреА рд╕реНрд╡рддрдГрдЪреА рдЫреЛрдЯреА рдХрд┐рд▓реНрд▓реА рдЕрд╕рддреЗ (data key).

рдЫреЛрдЯреНрдпрд╛ рдХрд┐рд▓реНрд▓реНрдпрд╛ рдХрдкрд╛рдЯрд╛рдВрдЪреНрдпрд╛ рд╢реЗрдЬрд╛рд░реА рдЦреБрдВрдЯреАрд╡рд░ рдЯрд╛рдВрдЧрд▓реНрдпрд╛ рддрд░ рдЪреЛрд░ рддреНрдпрд╛ рд╕рдЧрд│реНрдпрд╛ рдШреЗрдКрди рдЬрд╛рддреЛ. рдореНрд╣рдгреВрди office рдкреНрд░рддреНрдпреЗрдХ рдЫреЛрдЯреА рдХрд┐рд▓реНрд▓реА рдПрдХрд╛ рдЫреЛрдЯреНрдпрд╛ рдХреБрд▓реВрдкрдмрдВрдж рдбрдмреАрдд рдареЗрд╡рддреЗ, рдЖрдгрд┐ рддреНрдпрд╛ рдбрдмреНрдпрд╛ рдлрдХреНрдд рдореБрдЦреНрдп office рдЪреА рддрд┐рдЬреЛрд░реА рдЙрдШрдбреВ рд╢рдХрддреЗ (KMS рдордзрд▓реА master key). рдореБрдЦреНрдп office рдЪреА рддрд┐рдЬреЛрд░реА рдХрдзреАрдЪ рдореБрдЦреНрдп office рд╕реЛрдбрдд рдирд╛рд╣реА. рддреА рдЙрд╕рдиреА рдШреЗрддрд╛ рдпреЗрдд рдирд╛рд╣реА. рддреБрдореНрд╣реА рдлрдХреНрдд рдХреБрд▓реВрдкрдмрдВрдж рдбрдмреА рдШреЗрдКрди рдЬрд╛рдКрди рд╡рд┐рдЪрд╛рд░реВ рд╢рдХрддрд╛: "рдХреГрдкрдпрд╛ рд╣реА рдЙрдШрдбрд╛" тАФ рдЖрдгрд┐ рдореБрдЦреНрдп office рдХреЛрдгреА рд╡рд┐рдЪрд╛рд░рд▓реЗ, рдЖрдгрд┐ рдХрдзреА, рддреЗ рд▓рд┐рд╣реВрди рдареЗрд╡рддреЗ.

рдореНрд╣рдгреВрди рдкреНрд░рддреНрдпреЗрдХ рдХрдкрд╛рдЯрд╛рд╢реЗрдЬрд╛рд░реА рджреЛрди рдЧреЛрд╖реНрдЯреА рдЕрд╕рддрд╛рдд: рдХреБрд▓реВрдкрдмрдВрдж рдкреНрд░рдЧрддреАрдкреБрд╕реНрддрдХреЗ, рдЖрдгрд┐ рддрд┐рдЪреНрдпрд╛ рдХреБрд▓реВрдкрдмрдВрдж рдбрдмреАрддрд▓реА рдЫреЛрдЯреА рдХрд┐рд▓реНрд▓реА. рджреЛрдиреНрд╣реА рдЪреЛрд░рдгрд╛рд▒реНрдпрд╛ рдЪреЛрд░рд╛рдХрдбреЗ рдЙрдШрдбрддрд╛ рдпреЗрдИрд▓ рдЕрд╕реЗ рдХрд╛рд╣реАрдЪ рдирд╕рддреЗ.

рдЖрдгрд┐ office рдПрдХ metal detector рдШреЗрдКрди рдлрд┐рд░рддреЗ (secret scanner). рдХреЛрдгреА noticeboard рд╡рд░ рдХрд┐рд▓реНрд▓реА рдЪрд┐рдХрдЯрд╡рд▓реА рдЕрд╕реЗрд▓ тАФ file рдордзрд▓рд╛ password тАФ рддрд░ рддреЛ рд╡рд╛рдЬрддреЛ.

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

flowchart LR
    pt["ЁЯУД Katrina: A+ in maths"] --> enc["ЁЯФР encrypt with data key"]
    dk["ЁЯФС data key<br/>fresh per object"] --> enc
    enc --> ct["ciphertext"]
    dk --> wrap["ЁЯУж wrap with master key"]
    kms["ЁЯПж KMS master key<br/>never leaves KMS"] --> wrap
    wrap --> wk["wrapped data key"]
    ct --> store["ЁЯЧДя╕П stored together:<br/>ciphertext + wrapped key"]
    wk --> store
    scan["ЁЯФН secret scanner<br/>AKIAтАж ┬╖ PRIVATE KEY ┬╖ password="] -.-> repo["ЁЯУБ repo files"]

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг code рдордзрд▓реА рдЧреБрдкрд┐рддреЗ code рдЬрд┐рдереЗ рдЬрд╛рддреЛ рддрд┐рдереЗ рд╕рдЧрд│реАрдХрдбреЗ рдЬрд╛рддрд╛рдд: рдкреНрд░рддреНрдпреЗрдХ laptop, рдкреНрд░рддреНрдпреЗрдХ fork, рдкреНрд░рддреНрдпреЗрдХ CI log, рдкреНрд░рддреНрдпреЗрдХ container image тАФ рдЖрдгрд┐ рддреБрдореНрд╣реА рдУрд│ delete рдХреЗрд▓реНрдпрд╛рдирдВрддрд░рд╣реА git history рддреА рдареЗрд╡рддреЗ. рдХрд╛рд░рдг manager рддреБрдореНрд╣рд╛рд▓рд╛ files рджреЗрдК рд╢рдХрдд рдирд╛рд╣реАрдд рддреЗ рджреЗрддреЛ: рдЧреБрдкрд┐рдд рдХреЛрдгреА рд╡рд╛рдЪрд▓реЗ, release рд╢рд┐рд╡рд╛рдп рддреЗ рдмрджрд▓рдгреНрдпрд╛рдЪрд╛ рдорд╛рд░реНрдЧ, рдЖрдгрд┐ access рдХрд╛рдвреВрди рдШреЗрдгреНрдпрд╛рдЪрд╛ рдорд╛рд░реНрдЧ. рдЖрдгрд┐ рдХрд╛рд░рдг envelope encryption рдорд╣рддреНрддреНрд╡рд╛рдЪреА рдПрдХрдореЗрд╡ key KMS рдЪреНрдпрд╛ рдЖрдд рдареЗрд╡рддреЗ: data рд╡рд╛рдЪрдгреНрдпрд╛рд╕рд╛рдареА рддреБрдореНрд╣рд╛рд▓рд╛ рд╕рд╛рдард╡рд▓реЗрд▓рд╛ blob рдЖрдгрд┐ kms:Decrypt call рдХрд░рдгреНрдпрд╛рдЪреА permission рджреЛрдиреНрд╣реА рд▓рд╛рдЧрддрд╛рдд тАФ рдЬреА рддреБрдореНрд╣реА audit рдХрд░реВ рд╢рдХрддрд╛, рдЖрдгрд┐ revoke рдХрд░реВ рд╢рдХрддрд╛.

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

sec/cloud.py рдордзреНрдпреЗ: envelope_encrypt(plaintext, master_key, data_key) ciphertext (data key рдЦрд╛рд▓рдЪрд╛ data) рдЖрдгрд┐ wrapped_key (master key рдЦрд╛рд▓рдЪреА data key) рдкрд░рдд рджреЗрддреЗ. envelope_decrypt(blob, master_key) рдЖрдзреА data key рдЙрдШрдбрддреЗ, рдордЧ decrypt рдХрд░рддреЗ тАФ kms:Decrypt + local decryption рд╕рд╛рд░рдЦреНрдпрд╛рдЪ рджреЛрди рдкрд╛рдпрд▒реНрдпрд╛. find_secrets(files) {filename: text} рдЪреНрдпрд╛ dict рд╡рд░ рдЪрд╛рд░ regular expressions рдЪрд╛рд▓рд╡рддреЗ рдЖрдгрд┐ (file, kind) рдЬреЛрдбреНрдпрд╛ рдкрд░рдд рджреЗрддреЗ.

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

python3 sec/demo.py secrets
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import envelope_encrypt, envelope_decrypt, find_secrets
blob = envelope_encrypt("Dipika: B+ in maths", master_key=b"kms-master-key", data_key=b"fresh-data-key-42")
print("stored next to the data:", sorted(blob))
print("right master key тЖТ", envelope_decrypt(blob, b"kms-master-key"))
try:
    print("wrong master key тЖТ", envelope_decrypt(blob, b"guess"))
except UnicodeDecodeError:
    print("wrong master key тЖТ unreadable bytes")
files = {"settings.py": 'DB_PASSWORD = os.environ["DB_PASSWORD"]',
         "docker-compose.yml": "DATABASE_URL: postgres://app:Winter2026@db:5432/school",
         "test_login.py": 'token = "abc"',
         "old.env": "API_TOKEN='sk_live_0123456789abcdef'"}
for name, kind in find_secrets(files): print(f"found: {name:<20} {kind}")
EOF

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

secrets рд╣реЗ print рдХрд░рддреЗ:

тФАтФА envelope encryption: ciphertext fe1cf83a01159db61697тАж ┬╖ wrapped data key 00d66fb446efd05f5b12тАж
   decrypt = unwrap the data key with the master key (KMS) тЖТ 'Katrina: A+ in maths'
тФАтФА secret scan of 4 files тЖТ [('config.py', 'generic secret'), ('deploy.sh', 'AWS access key id'), ('notes.md', 'private key')]
   secrets live in a manager (Secrets Manager / Vault), are fetched at runtime, rotated, and never committed

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

stored next to the data: ['ciphertext', 'wrapped_key']
right master key тЖТ Dipika: B+ in maths
wrong master key тЖТ unreadable bytes
found: docker-compose.yml   password in a URL
found: old.env              generic secret

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

рдлрдХреНрдд рджреЛрдирдЪ рдЧреЛрд╖реНрдЯреА рд╕рд╛рдард╡рд▓реНрдпрд╛ рдЬрд╛рддрд╛рдд тАФ ciphertext рдЖрдгрд┐ wrapped key; plain data key рдирд┐рдШреВрди рдЧреЗрд▓реА рдЖрд╣реЗ. Master key рд╢рд┐рд╡рд╛рдп data рд╡рд╛рдЪрддрд╛ рдпреЗрдд рдирд╛рд╣реА. Scanner рд▓рд╛ database URL рдордзрд▓рд╛ password рдЖрдгрд┐ рдЬреБрдиреНрдпрд╛ .env file рдордзрд▓рд╛ token рд╕рд╛рдкрдбрд▓рд╛. рддреНрдпрд╛рдиреЗ environment рдордзреВрди password рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ settings.py рд▓рд╛, рдХрд┐рдВрд╡рд╛ 3 рдЕрдХреНрд╖рд░рд╛рдВрдЪреНрдпрд╛ test value рд▓рд╛ flag рдХреЗрд▓реЗ рдирд╛рд╣реА. Scanners patterns рдЖрдгрд┐ рд▓рд╛рдВрдмреАрдЪреЗ рдирд┐рдпрдо рд╡рд╛рдкрд░рддрд╛рдд тАФ рддреЗ рдЕрдиреЗрдХ leaks рдкрдХрдбрддрд╛рдд, рд╕рдЧрд│реЗ рдирд╛рд╣реА, рдореНрд╣рдгреВрди рддреЗ рдПрдХ рдерд░ рдЖрд╣реЗрдд, рд╕рдВрдкреВрд░реНрдг рдЖрд░рд╛рдЦрдбрд╛ рдирд╛рд╣реА.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ database рдЧреБрдкрд┐рдд AWS Secrets Manager рдордзреНрдпреЗ, рддреБрдордЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ KMS key рдиреЗ encrypted, рд╕рд╛рдард╡рд╛ рдЖрдгрд┐ automatic rotation рдЪрд╛рд▓реВ рдХрд░рд╛:

aws kms create-key --description "school secrets"          # note the KeyId
aws kms create-alias --alias-name alias/school-secrets --target-key-id <KeyId>
aws kms enable-key-rotation --key-id <KeyId>
aws secretsmanager create-secret --name school/results-db \
    --kms-key-id alias/school-secrets --secret-string file://results-db.json
aws secretsmanager rotate-secret --secret-id school/results-db \
    --rotation-lambda-arn arn:aws:lambda:ap-south-1:111122223333:function:rotate-results-db \
    --rotation-rules AutomaticallyAfterDays=30

App рдЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ IAM role рдиреЗ рддреЗ runtime рд▓рд╛ рд╡рд╛рдЪрд╛ (Python, boto3) тАФ code рдХрд┐рдВрд╡рд╛ image рдордзреНрдпреЗ рдХрд╛рд╣реАрдЪ рдирд╛рд╣реА:

import boto3, json
sm = boto3.client("secretsmanager")
db = json.loads(sm.get_secret_value(SecretId="school/results-db")["SecretString"])

KMS data key рдЖрдгрд┐ AES-256-GCM рд╕рд╣ envelope encryption (cryptography library):

import boto3, os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client("kms")
dk = kms.generate_data_key(KeyId="alias/school-data", KeySpec="AES_256")
nonce = os.urandom(12)
ciphertext = AESGCM(dk["Plaintext"]).encrypt(nonce, b"Katrina: A+ in maths", None)
record = {"ciphertext": ciphertext, "nonce": nonce, "wrapped_key": dk["CiphertextBlob"]}
del dk                                                     # never store the plain data key
# to read: key = kms.decrypt(CiphertextBlob=record["wrapped_key"])["Plaintext"]
#          AESGCM(key).decrypt(record["nonce"], record["ciphertext"], None)

(AWS Encryption SDK рд╣реЗ рд╕рдЧрд│реЗ рддреБрдордЪреНрдпрд╛рд╕рд╛рдареА рдХрд░рддреЗ, key caching рд╕рд╣.)

рдЧреБрдкрд┐рддреЗ commit рд╣реЛрдгреНрдпрд╛рдЖрдзреАрдЪ рдерд╛рдВрдмрд╡рд╛ тАФ gitleaks рд╕рд╣ pre-commit hook:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рддреБрдордЪреНрдпрд╛ code host рдЪреЗ secret scanning push protection рд╕рд╣ рдЪрд╛рд▓реВ рдХрд░рд╛, CI рдордзреНрдпреЗ рдПрдХрджрд╛ рдкреВрд░реНрдг history рд╡рд░ scanner рдЪрд╛рд▓рд╡рд╛, рдЖрдгрд┐ рд╕рд╛рдкрдбрд▓реЗрд▓реЗ рдкреНрд░рддреНрдпреЗрдХ рдЧреБрдкрд┐рдд manager рдордзреНрдпреЗ рд╣рд▓рд╡рд╛ тАФ рдЬрд╛рддрд╛ рдЬрд╛рддрд╛ рддреЗ rotate рдХрд░рдд. Access keys рдРрд╡рдЬреА IAM roles рд▓рд╛ рдкреНрд░рд╛рдзрд╛рдиреНрдп рджреНрдпрд╛, рдореНрд╣рдгрдЬреЗ leak рд╣реЛрдгреНрдпрд╛рд╕рд╛рдареА рджреАрд░реНрдШрд╛рдпреБрд╖реА key рдореБрд│рд╛рддрдЪ рдирд╕реЗрд▓.

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

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

git checkout lesson-08-iam-network

ЁЯЧЭя╕П Lesson 07 тАФ Secrets and KMS: keys in a vault, not in code

ЁЯУН You are here: Lesson 07 of 16 ┬╖ Previous: lesson-06-authorization ┬╖ Next: lesson-08-iam-network


ЁЯУж What's in this branch

Lessons 01тАУ06, plus the first lesson of the building desk. Two jobs. First, envelope encryption: a fresh data key encrypts the data, and a master key that never leaves KMS encrypts only the data key. Second, secrets тАФ database passwords, API keys тАФ live in a secret manager, not in files, and a secret scanner finds the ones that slipped into code. secrets() in sec/demo.py shows both.

тЪая╕П The cipher in sec/cloud.py is a teaching keystream built from SHA-256. It only shows the shape of envelope encryption. It has no integrity check and must never protect real data. Real systems use AWS KMS and a vetted library (AES-256-GCM). Never write your own cryptography.

ЁЯзТ Explain like I'm 5

The school keeps report cards in a cupboard. Each cupboard has its own small key (the data key).

If the small keys hang on a hook next to the cupboards, a thief takes them all. So the office puts every small key into a tiny locked box, and only the head office safe can open those boxes (the master key in KMS). The head office safe never leaves the head office. You cannot borrow it. You can only bring a locked box and ask: "please open this" тАФ and the head office writes down who asked, and when.

So next to each cupboard sit two things: the locked report cards, and the small key in its locked box. A thief who steals both has nothing he can open.

And the office walks around with a metal detector (the secret scanner). If someone has taped a key to a noticeboard тАФ a password in a file тАФ it beeps.

ЁЯЧ║я╕П Diagram

flowchart LR
    pt["ЁЯУД Katrina: A+ in maths"] --> enc["ЁЯФР encrypt with data key"]
    dk["ЁЯФС data key<br/>fresh per object"] --> enc
    enc --> ct["ciphertext"]
    dk --> wrap["ЁЯУж wrap with master key"]
    kms["ЁЯПж KMS master key<br/>never leaves KMS"] --> wrap
    wrap --> wk["wrapped data key"]
    ct --> store["ЁЯЧДя╕П stored together:<br/>ciphertext + wrapped key"]
    wk --> store
    scan["ЁЯФН secret scanner<br/>AKIAтАж ┬╖ PRIVATE KEY ┬╖ password="] -.-> repo["ЁЯУБ repo files"]

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

тЭУ What

ЁЯдФ Why

Because secrets in code travel everywhere the code goes: every laptop, every fork, every CI log, every container image тАФ and git history keeps them after you delete the line. Because a manager gives you what files cannot: who read the secret, a way to change it without a release, and a way to take access away. And because envelope encryption keeps the one key that matters inside KMS: to read data, you need both the stored blob and permission to call kms:Decrypt тАФ which you can audit, and revoke.

ЁЯФз How (in this repo)

In sec/cloud.py: envelope_encrypt(plaintext, master_key, data_key) returns ciphertext (the data under the data key) and wrapped_key (the data key under the master key). envelope_decrypt(blob, master_key) unwraps the data key, then decrypts тАФ the same two steps as kms:Decrypt + local decryption. find_secrets(files) runs four regular expressions over a dict of {filename: text} and returns (file, kind) pairs.

ЁЯзк Try it

python3 sec/demo.py secrets
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from cloud import envelope_encrypt, envelope_decrypt, find_secrets
blob = envelope_encrypt("Dipika: B+ in maths", master_key=b"kms-master-key", data_key=b"fresh-data-key-42")
print("stored next to the data:", sorted(blob))
print("right master key тЖТ", envelope_decrypt(blob, b"kms-master-key"))
try:
    print("wrong master key тЖТ", envelope_decrypt(blob, b"guess"))
except UnicodeDecodeError:
    print("wrong master key тЖТ unreadable bytes")
files = {"settings.py": 'DB_PASSWORD = os.environ["DB_PASSWORD"]',
         "docker-compose.yml": "DATABASE_URL: postgres://app:Winter2026@db:5432/school",
         "test_login.py": 'token = "abc"',
         "old.env": "API_TOKEN='sk_live_0123456789abcdef'"}
for name, kind in find_secrets(files): print(f"found: {name:<20} {kind}")
EOF

тЬЕ Verify тАФ what you should see

secrets prints:

тФАтФА envelope encryption: ciphertext fe1cf83a01159db61697тАж ┬╖ wrapped data key 00d66fb446efd05f5b12тАж
   decrypt = unwrap the data key with the master key (KMS) тЖТ 'Katrina: A+ in maths'
тФАтФА secret scan of 4 files тЖТ [('config.py', 'generic secret'), ('deploy.sh', 'AWS access key id'), ('notes.md', 'private key')]
   secrets live in a manager (Secrets Manager / Vault), are fetched at runtime, rotated, and never committed

Your snippet prints:

stored next to the data: ['ciphertext', 'wrapped_key']
right master key тЖТ Dipika: B+ in maths
wrong master key тЖТ unreadable bytes
found: docker-compose.yml   password in a URL
found: old.env              generic secret

ЁЯПБ What you just proved

Only two things are stored тАФ the ciphertext and the wrapped key; the plain data key is gone. Without the master key, the data stays unreadable. The scanner found the password inside a database URL and the token in an old .env file. It did not flag settings.py, which reads the password from the environment, or the 3-character test value. Scanners use patterns and length rules тАФ they catch many leaks, not all, so they are one layer, not the plan.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ store a database secret in AWS Secrets Manager, encrypted with your own KMS key, and turn on automatic rotation:

aws kms create-key --description "school secrets"          # note the KeyId
aws kms create-alias --alias-name alias/school-secrets --target-key-id <KeyId>
aws kms enable-key-rotation --key-id <KeyId>
aws secretsmanager create-secret --name school/results-db \
    --kms-key-id alias/school-secrets --secret-string file://results-db.json
aws secretsmanager rotate-secret --secret-id school/results-db \
    --rotation-lambda-arn arn:aws:lambda:ap-south-1:111122223333:function:rotate-results-db \
    --rotation-rules AutomaticallyAfterDays=30

Read it at runtime, with the app's own IAM role (Python, boto3) тАФ nothing in code or image:

import boto3, json
sm = boto3.client("secretsmanager")
db = json.loads(sm.get_secret_value(SecretId="school/results-db")["SecretString"])

Envelope encryption with a KMS data key and AES-256-GCM (cryptography library):

import boto3, os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client("kms")
dk = kms.generate_data_key(KeyId="alias/school-data", KeySpec="AES_256")
nonce = os.urandom(12)
ciphertext = AESGCM(dk["Plaintext"]).encrypt(nonce, b"Katrina: A+ in maths", None)
record = {"ciphertext": ciphertext, "nonce": nonce, "wrapped_key": dk["CiphertextBlob"]}
del dk                                                     # never store the plain data key
# to read: key = kms.decrypt(CiphertextBlob=record["wrapped_key"])["Plaintext"]
#          AESGCM(key).decrypt(record["nonce"], record["ciphertext"], None)

(The AWS Encryption SDK does all of this for you, including the key caching.)

Stop secrets before they are committed тАФ a pre-commit hook with gitleaks:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2
    hooks:
      - id: gitleaks

ЁЯПн Why this matters in production: turn on your code host's secret scanning with push protection, run a scanner in CI over the full history once, and move every secret you find into a manager тАФ rotating it as you go. Prefer IAM roles over access keys, so there is no long-lived key to leak at all.

тПня╕П Next

The secret is safe. But the role that reads it may be allowed to do far more than it needs, and the database may be reachable from anywhere. Next: least privilege and closed doors.

git checkout lesson-08-iam-network
тЖР PreviousauthorizationNext тЖТiam network

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