ЁЯФР рдзрдбрд╛ 06 тАФ рдЖрдд рдХреЛрдг рдпреЗрдК рд╢рдХрддреЛ: IAM, JWT рдЖрдгрд┐ Lambda authorizers
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 06 ┬╖ рдорд╛рдЧреЗ: lesson-05-stages-deployments ┬╖ рдкреБрдвреЗ: lesson-07-throttling
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ05, рдЖрдгрд┐ badge рдЪреА рддрдкрд╛рд╕рдгреА: JWT authorizer (signature, issuer, audience,
expiry, рдордЧ scope), Lambda authorizer (рддреБрдордЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ code рдирд┐рд░реНрдгрдп рдШреЗрддреЛ), AWS рдордзрд▓реНрдпрд╛
callers рд╕рд╛рдареА IAM (SigV4) тАФ рдЖрдгрд┐ API key рдореНрд╣рдгрдЬреЗ authentication рдХрд╛ рдирд╛рд╣реА. рдЗрдереЗ рднрд╛рдЧ 1:
front office рд╕рдВрдкрддреЛ. apigw/demo.py рдордзрд▓реЗ auth() рдкрд╛рдЪ tokens рдЖрдгрд┐ рджреЛрди
badges рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рддреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
Desk рд╡рд░ clerk badge ЁЯкк рдорд╛рдЧрддреЛ. рдЪрд╛рдВрдЧрд▓реНрдпрд╛ badge рдордзреНрдпреЗ рдЪрд╛рд░ рдЧреЛрд╖реНрдЯреА рдЕрд╕рддрд╛рдд рдЬреНрдпрд╛ clerk рддрдкрд╛рд╕рддреЛ:
- рд╢рд┐рдХреНрдХрд╛ рдЦрд░рд╛ рдЖрд╣реЗ (рдкреЗрдирдиреЗ рдХрд╛рдврд▓реЗрд▓рд╛ рдирд╛рд╣реА) тАФ signature;
- рддреЛ рдЖрдкрд▓реНрдпрд╛рдЪ badge office рдХрдбреВрди рдЖрд▓рд╛ рдЖрд╣реЗ тАФ issuer;
- рддреЛ рдпрд╛ рд╢рд╛рд│реЗрд╕рд╛рдареА рдмрдирд╡рд▓рд╛ рдЖрд╣реЗ, рд╢реЗрдЬрд╛рд░рдЪреНрдпрд╛ library рд╕рд╛рдареА рдирд╛рд╣реА тАФ audience;
- рддрд╛рд░реАрдЦ рдЙрд▓рдЯреВрди рдЧреЗрд▓реЗрд▓реА рдирд╛рд╣реА тАФ expiry.
рдХреЛрдгрддреАрд╣реА рддрдкрд╛рд╕рдгреА рдЕрдкрдпрд╢реА тЖТ "рддреБрдореНрд╣реА рдХреЛрдг?" (401). рдЪрд╛рд░рд╣реА рдкрд╛рд╕, рдкрдг badge рд╡рд░ "marks рд╡рд╛рдЪреВ
рд╢рдХрддреЛ" рдЕрд╕реЗ рдЖрд╣реЗ рдЖрдгрд┐ visitor рд▓рд╛ marks рд▓рд┐рд╣рд╛рдпрдЪреЗ рдЖрд╣реЗрдд тЖТ "рдореА рддреБрдореНрд╣рд╛рд▓рд╛ рдУрд│рдЦрддреЛ, рдкрдг рдирд╛рд╣реА" (403).
рдХрд╛рд╣реА рджрд╛рд░рд╛рдВрд╡рд░ рд╕реНрд╡рддрдГрдЪрд╛ рдирд┐рдпрдо рдЕрд╕рд▓реЗрд▓рд╛ рдЦрд╛рд╕ рдкрд╣рд╛рд░реЗрдХрд░реА ЁЯзСтАНтЬИя╕П рдЕрд╕рддреЛ тАФ "рдлрдХреНрдд T рдиреЗ рд╕реБрд░реВ рд╣реЛрдгрд╛рд░реЗ staff badges". рддреЛ рдореНрд╣рдгрдЬреЗ Lambda authorizer.
рдЖрдгрд┐ partners рджрд╛рдЦрд╡рддрд╛рдд рддреЛ visitor pass number? рддреЛ clerk рд▓рд╛ рдлрдХреНрдд рдХреЛрдгрддрд╛ partner рднреЗрдЯреА рдореЛрдЬрддреЛ рдЖрд╣реЗ рддреЗ рд╕рд╛рдВрдЧрддреЛ. рддреЛ number copy рдХрд░рдгрд╛рд░рд╛ рдХреЛрдгреАрд╣реА рддреЛ рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ. рддреЛ badge рдирд╛рд╣реА.
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
req["ЁЯЩЛ GET /students/me<br/>Authorization: Bearer тАж"] --> s{"ЁЯФП signature?"}
s -->|"forged"| u["401 bad signature"]
s --> i{"ЁЯПл issuer?"}
i -->|"other issuer"| u2["401 wrong issuer"]
i --> a{"ЁЯОп audience = school-api?"}
a -->|"library-api"| u3["401 wrong audience"]
a --> e{"тП░ expired?"}
e -->|"yes"| u4["401 expired"]
e --> sc{"ЁЯУЬ scope?"}
sc -->|"needs marks:write"| f["403 Forbidden"]
sc --> ok["тЬЕ 200 тАФ principal teacher-42"]
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/apigateway/lesson-diagrams.html#l06
тЭУ рдХрд╛рдп
- 401 рд╡рд┐рд░реБрджреНрдз 403 тАФ
401 Unauthorized: рддреБрдореНрд╣реА рдХреЛрдг рдЖрд╣рд╛рдд рддреЗ office рд▓рд╛ рдорд╛рд╣реАрдд рдирд╛рд╣реА (token рдирд╛рд╣реА, рд╡рд╛рдИрдЯ token).403 Forbidden: рддреБрдореНрд╣реА рдХреЛрдг рддреЗ рддреНрдпрд╛рд▓рд╛ рдорд╛рд╣реАрдд рдЖрд╣реЗ, рдЖрдгрд┐ рдЙрддреНрддрд░ рдирд╛рд╣реА рдЖрд╣реЗ. - IAM authorization (SigV4) тАФ caller рдкреНрд░рддреНрдпреЗрдХ request рд╡рд░ AWS credentials рдиреЗ рд╕рд╣реА рдХрд░рддреЛ;
office signature рдЖрдгрд┐ caller рдЪреА
execute-api:Invokeрд╕рд╛рдареАрдЪреА IAM policy рддрдкрд╛рд╕рддреЗ. AWS рдордзрд▓реНрдпрд╛ callers рд╕рд╛рдареА (services, roles) рд╕рд░реНрд╡реЛрддреНрддрдо. REST рдЖрдгрд┐ HTTP APIs. - Cognito user pool authorizer (REST APIs) тАФ office Amazon Cognito user pool рдиреЗ рджрд┐рд▓реЗрд▓рд╛ token рддрдкрд╛рд╕рддреЗ.
- JWT authorizer (HTTP APIs) тАФ Cognito рдХрд┐рдВрд╡рд╛ рдХреЛрдгрддреНрдпрд╛рд╣реА OIDC issuer рд╕рд╛рдареА. рддреЗ issuer
рдЪреНрдпрд╛ public keys (JWKS) рдЖрдгрддреЗ рдЖрдгрд┐ signature,
iss,aud(рдХрд┐рдВрд╡рд╛client_id),exp, рдЖрдгрд┐ рдРрдЪреНрдЫрд┐рдХ scopes рддрдкрд╛рд╕рддреЗ. Code рд▓рд┐рд╣рд╛рдпрдЪреА рдЧрд░рдЬ рдирд╛рд╣реА. - Lambda authorizer тАФ рддреБрдордЪреНрдпрд╛ function рд▓рд╛ token (TOKEN type) рдХрд┐рдВрд╡рд╛ request рдЪреЗ рднрд╛рдЧ (REQUEST type) рдорд┐рд│рддрд╛рдд рдЖрдгрд┐ рддреЗ IAM policy (рдХрд┐рдВрд╡рд╛ HTTP APIs рд╡рд░ рд╕рд╛рдзреЗ рд╣реЛ/рдирд╛рд╣реА) рдкрд░рдд рджреЗрддреЗ. рддреНрдпрд╛рдЪреЗ рдЙрддреНрддрд░ identity рдиреБрд╕рд╛рд░ рдПрдХрд╛ TTL рд╕рд╛рдареА cache рд╣реЛрддреЗ тАФ default 300 seconds (0 рдиреЗ caching рдмрдВрдж рд╣реЛрддреЗ; рдЬрд╛рд╕реНрддреАрдд рдЬрд╛рд╕реНрдд 3600).
- Resource policy (REST APIs) тАФ API рд╡рд░рдЪ рдЕрд╕рд▓реЗрд▓реА policy: рдлрдХреНрдд рдХрд╛рд╣реА accounts, IP ranges рдХрд┐рдВрд╡рд╛ VPC endpoints рдирд╛ рдкрд░рд╡рд╛рдирдЧреА (рдзрдбрд╛ 12).
- API keys тАФ usage plans рд╕рд╛рдареА client рдУрд│рдЦрддрд╛рдд (рдзрдбрд╛ 07). Key рдореНрд╣рдгрдЬреЗ рд╡рд╛рдЯреВрди рдШреЗрддрд▓реЗрд▓реА string: рддреА apps, logs рдЖрдгрд┐ screenshots рдордзреВрди рдмрд╛рд╣реЗрд░ рдкрдбрддреЗ. рддреА рдХреЛрдгрддрд╛ client рддреЗ рд╕рд╛рдВрдЧрддреЗ, рдХреЛрдг рддреЗ рдирд╛рд╣реА. рдХрдзреАрд╣реА рдПрдХрдореЗрд╡ рд╕рдВрд░рдХреНрд╖рдг рдирд╛рд╣реА.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рд╕реНрд╡рддрдГ tokens рддрдкрд╛рд╕рдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ back end рдореНрд╣рдгрдЬреЗ expiry рдХрд┐рдВрд╡рд╛ audience рд╡рд┐рд╕рд░рдгреНрдпрд╛рдЪреА рдЖрдгрдЦреА
рдПрдХ рдЬрд╛рдЧрд╛. рджрд╛рд░рд╛рд╡рд░рдЪрд╛ authorizer рд╣реА рддрдкрд╛рд╕рдгреА рдПрдХрджрд╛рдЪ, рдПрдХрд╛рдЪ рдкрджреНрдзрддреАрдиреЗ, рдХреЛрдгрддрд╛рд╣реА kitchen code
рдЪрд╛рд▓рдгреНрдпрд╛рдЖрдзреА рдХрд░рддреЛ тАФ рдЖрдгрд┐ рддрдкрд╛рд╕рд▓реЗрд▓реА identity (principal) рдЖрдд рдкрд╛рдард╡рддреЛ, рдореНрд╣рдгрдЬреЗ kitchen рддрд┐рдЪреНрдпрд╛рд╡рд░
рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡реВ рд╢рдХрддреЗ. Audience рдЪреА рддрдкрд╛рд╕рдгреА рд╕рд░реНрд╡рд╛рдд рдорд╣рддреНрддреНрд╡рд╛рдЪреА: рддреА рдирд╕реЗрд▓ рддрд░ library app рд╕рд╛рдареА рдмрдирд╡рд▓реЗрд▓рд╛
token рд╢рд╛рд│реЗрдЪрд╛ API рдЙрдШрдбреЗрд▓.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
apigw/gateway.py рдордзрд▓реЗ check_jwt() рдЪрд╛рд░ рддрдкрд╛рд╕рдгреНрдпрд╛ рдХреНрд░рдорд╛рдиреЗ рдХрд░рддреЗ
рдЖрдгрд┐ (claims, None) рдХрд┐рдВрд╡рд╛ (None, reason) рдкрд░рдд рджреЗрддреЗ. Gateway._handle() рдХрд╛рд░рдгрд╛рд╕рд╣ 401
рдкрд░рдд рджреЗрддреЗ, рдордЧ route.scope рддрдкрд╛рд╕рддреЗ (POST /marks рд▓рд╛ marks:write рд▓рд╛рдЧрддреЛ) рдЖрдгрд┐ рддреЛ рдирд╕реЗрд▓ рддрд░
403 рдкрд░рдд рджреЗрддреЗ; token рдЪрд╛ sub request рдЪрд╛ principal рдмрдирддреЛ.
route.auth == "lambda" рд╕рд╛рдареА рддреЗ apigw/demo.py рдордзрд▓реЗ
teacher_badge() call рдХрд░рддреЗ, рдЬреЗ рдлрдХреНрдд badge-T-42 рд╕реНрд╡реАрдХрд╛рд░рддреЗ. Demo рдордзрд▓реЗ token()
teacher-42 рд╕рд╛рдареА рдПрдХрд╛ рддрд╛рд╕рд╛рдЪрд╛ рдпреЛрдЧреНрдп token рдмрдирд╡рддреЗ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 apigw/demo.py auth
python3 - <<'EOF'
import sys; sys.path.insert(0, "apigw"); from demo import office, token, JWT
from gateway import make_jwt, check_jwt
gw, c = office()
now = c()
print(check_jwt("hello", **JWT, now=now))
print(check_jwt(make_jwt(dict(iss="https://evil.example", aud="school-api", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud=["library-api", "school-api"], sub="katrina", exp=now + 60), JWT["secret"]), **JWT, now=now))
print(check_jwt(make_jwt(dict(iss=JWT["issuer"], aud="school-api", exp=now + 60), "guessed-secret"), **JWT, now=now))
w = token(c, scope="marks:read marks:write")
print(gw.handle("POST", "/marks", {"Authorization": "Bearer " + w}, {"student": "9", "subject": "science", "mark": 88})[0::2])
print(gw.handle("GET", "/staffroom", {"Authorization": "badge-T-42"})[0::2], gw.logs[-1]["principal"])
EOF
рдЪрд╛рд▓реВ office рд╡рд░, рдШрдбреНрдпрд╛рд│рд╛рд╡рд░реВрди рдмрдирд╡рд▓реЗрд▓реНрдпрд╛ рдПрдХрд╛ рддрд╛рд╕рд╛рдЪреНрдпрд╛ рдЦрд▒реНрдпрд╛ token рд╕рд╣:
python3 apigw/demo.py serve &
sleep 1 # give the office a second to open
TOKEN=$(python3 -c "import sys,time; sys.path.insert(0,'apigw'); from demo import token; print(token(time.time))")
curl localhost:8080/students/me
curl -H "Authorization: Bearer $TOKEN" localhost:8080/students/me
curl -X POST -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"student":"7","subject":"maths","mark":91}' localhost:8080/marks
curl -H "Authorization: badge-T-42" localhost:8080/staffroom
kill %1
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
auth рдкреНрд░рддреНрдпреЗрдХ token рд╕рд╛рдареА рдПрдХ, рдЕрд╕реЗ print рдХрд░рддреЗ:
no token тЖТ 401 {'message': 'Unauthorized', 'why': 'no token'},
forged тЖТ 401 тАж 'bad signature', expired тЖТ 401 тАж 'expired',
other app's token тЖТ 401 тАж 'wrong audience', good тЖТ 200 {'you': 'teacher-42'};
рдордЧ POST /marks тЖТ 403 {'message': 'Forbidden', 'why': 'needs scope marks:write'};
рдордЧ Lambda authorizer:
badge-S-1 рд╕рд╛рдареА GET /staffroom тЖТ 403 {'message': 'User is not authorized to access this resource'}
рдЖрдгрд┐ badge-T-42 рд╕рд╛рдареА 200 {'you': 'teacher-42'}.
рддреБрдордЪрд╛ snippet (None, 'not a JWT'), (None, 'wrong issuer'), рдХрддрд░рд┐рдирд╛рдЪреНрдпрд╛ token рдЪреЗ claims
(school-api рдЕрд╕рд▓реЗрд▓реА aud list рд╕реНрд╡реАрдХрд╛рд░рд▓реА рдЬрд╛рддреЗ),
рдЕрдВрджрд╛рдЬрд╛рдиреЗ рд▓рд╛рд╡рд▓реЗрд▓реНрдпрд╛ secret рд╕рд╛рдареА (None, 'bad signature'),
рджреЛрдиреНрд╣реА scopes рдЕрд╕рд▓реЗрд▓реНрдпрд╛ token рд╕рд╛рдареА
(201, {'saved': {'student': '9', 'subject': 'science', 'mark': 88}, 'table': 'marks-prod'}),
рдЖрдгрд┐ (200, {'you': 'teacher-42'}) teacher-42 print рдХрд░рддреЛ.
рдЪрд╛рд▓реВ office {"message": "Unauthorized", "why": "no token"}, рдордЧ
{"you": "teacher-42"}, рдордЧ {"message": "Forbidden", "why": "needs scope marks:write"}
(default token рдордзреНрдпреЗ рдлрдХреНрдд marks:read рдЖрд╣реЗ), рдордЧ badge рд╕рд╛рдареА {"you": "teacher-42"} рдЕрд╕реЗ рдЙрддреНрддрд░ рджреЗрддреЗ.
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рдкреНрд░рддреНрдпреЗрдХ 401 рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ 403 рдЪреЗ рдХрд╛рд░рдг рддреБрдореНрд╣реА рд╕рд╛рдВрдЧреВ рд╢рдХрддрд╛: badge рд╡рд░ рдЪрд╛рд░ рддрдкрд╛рд╕рдгреНрдпрд╛, рдордЧ
scope рдЪреА рддрдкрд╛рд╕рдгреА тАФ рдЖрдгрд┐ Lambda authorizer рдореНрд╣рдгрдЬреЗ рддреНрдпрд╛рдЪ рджрд╛рд░рд╛рд╡рд░рдЪрд╛ рддреБрдордЪрд╛ рд╕реНрд╡рддрдГрдЪрд╛ рдирд┐рдпрдо.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- API key рд╣реЗрдЪ рдПрдХрдореЗрд╡ рд╕рдВрд░рдХреНрд╖рдг тАФ рддреА client рдУрд│рдЦрддреЗ; call рдХреЛрдг рдХрд░рддреЛрдп рддреЗ рд╕рд┐рджреНрдз рдХрд░рдд рдирд╛рд╣реА
- audience рддрдкрд╛рд╕рдгреА рдирд╕рд▓реЗрд▓рд╛ JWT authorizer (рдХрд┐рдВрд╡рд╛ company рдордзрд▓реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ app рдЪрд╛ audience)
- рдкреНрд░рддреНрдпреЗрдХ user рд╕рд╛рдареА рд╕рд╛рд░рдЦреНрдпрд╛рдЪ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ header рд╡рд░ cache рд╣реЛрдгрд╛рд░рд╛ Lambda authorizer тАФ рдПрдХрд╛ user рдЪреЗ "рд╣реЛ" рдЗрддрд░рд╛рдВрд╕рд╛рдареАрд╣реА рдЙрддреНрддрд░ рдард░рддреЗ
- gateway рдордзреНрдпреЗ token рддрдкрд╛рд╕рдгреЗ рдЖрдгрд┐ kitchen рдордзреНрдпреЗ request body рдордзрд▓реНрдпрд╛ user id рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рдгреЗ тАФ рддрдкрд╛рд╕рд▓реЗрд▓рд╛
principalрд╡рд╛рдкрд░рд╛ - browser app рд╕рд╛рдареА IAM auth тАФ SigV4 рд▓рд╛ browser рдордзреНрдпреЗ AWS credentials рд▓рд╛рдЧрддрд╛рдд; JWT рд╡рд╛рдкрд░рд╛
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
On a real account тАФ HTTP API рд╡рд░рдЪрд╛ JWT authorizer (Cognito рдХрд┐рдВрд╡рд╛ рдХреЛрдгрддрд╛рд╣реА OIDC issuer) рдЖрдгрд┐ scope рд▓рд╛рдЧрдгрд╛рд░рд╛ route:
aws apigatewayv2 create-authorizer --api-id a1b2c3 --name school-jwt \
--authorizer-type JWT --identity-source '$request.header.Authorization' \
--jwt-configuration Audience=school-api,Issuer=https://cognito-idp.ap-south-1.amazonaws.com/ap-south-1_AbCdEf123
aws apigatewayv2 update-route --api-id a1b2c3 --route-id r0ute1 \
--authorization-type JWT --authorizer-id au7h01 --authorization-scopes marks:write
AWS SAM тАФ рдкреВрд░реНрдг HTTP API рд╕рд╛рдареА рддреЗрдЪ:
SchoolApi:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
DefaultAuthorizer: SchoolJwt
Authorizers:
SchoolJwt:
IdentitySource: $request.header.Authorization
JwtConfiguration:
issuer: https://login.school.example
audience: [school-api]
300-second result cache рдЕрд╕рд▓реЗрд▓рд╛ REST API Lambda authorizer:
aws apigateway create-authorizer --rest-api-id abc123 --name staff-badge --type TOKEN \
--identity-source method.request.header.Authorization \
--authorizer-uri arn:aws:apigateway:ap-south-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-south-1:111122223333:function:staff-badge/invocations \
--authorizer-result-ttl-in-seconds 300
ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдкреНрд░рддреНрдпреЗрдХ route рд▓рд╛ authorizer рд╣рд╡рд╛, рдХрд┐рдВрд╡рд╛ рддреЛ public рдХрд╛ рдЖрд╣реЗ рдпрд╛рдЪреЗ рд▓рд┐рдЦрд┐рдд рдХрд╛рд░рдг. Reviews рддреАрди рдЧреЛрд╖реНрдЯреА рддрдкрд╛рд╕рддрд╛рдд: audience, рдкреНрд░рддреНрдпреЗрдХ route рдЪреЗ scopes, рдЖрдгрд┐ рдХреЛрдгрддрд╛рд╣реА route рдлрдХреНрдд API key рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдирд╛рд╣реА.
тПня╕П рдкреБрдвреЗ
рднрд╛рдЧ 1 рдкреВрд░реНрдг рдЭрд╛рд▓рд╛: рддреБрдореНрд╣реА рдХреЛрдг рдЖрд╣рд╛рдд рддреЗ office рд▓рд╛ рдорд╛рд╣реАрдд рдЖрд╣реЗ. рднрд╛рдЧ 2 рддреЗ рдЦрд░реЛрдЦрд░ рдЪрд╛рд▓рд╡рддреЛ, рд╕реБрд░реБрд╡рд╛рдд рджрд╛рд░рд╛рд╡рд░рдЪреНрдпрд╛ рдЧрд░реНрджреАрдкрд╛рд╕реВрди тАФ throttling рдЖрдгрд┐ usage plans.
git checkout lesson-07-throttling