Part 03 · High Level Design

Security and Cost in System Design

AuthN/AuthZEncryptionSecretsAbuse preventionCost

যেকোনো design review তে এই দুইটা প্রশ্ন আসেই: কে কী করতে পারবে, transit আর rest এ encryption, secret, abuse ঠেকানো, আর diagram এর প্রতিটা box এর জন্য মাসে কত খরচ।

⏱ 2-3 ঘণ্টা · Station 32 / 80

এক মাসে দুইটা চমক

চট্টগ্রামের একটা grocery delivery app একটা referral campaign চালু করল। এক সপ্তাহের মধ্যে দুইটা ঘটনা ঘটল।

একজন কৌতূহলী customer খেয়াল করল তার order page এর ঠিকানা /orders/48211। সে বদলে /orders/48210 দিল আর আরেক customer এর নাম, phone number আর বাসার ঠিকানা দেখে ফেলল। তারপর 48209। API দেখছিল user login করা আছে কিনা, কিন্তু order টা তার নিজের কিনা কখনো দেখেনি। একটা সাধারণ loop দিয়েই হাজার হাজার ঠিকানা পড়ে ফেলা যেত।

তারপর এলো cloud এর bill: আন্দাজের তিনগুণ। Server এর খরচ plan মতোই। যেটা কেউ plan করেনি সেটা হলো egress, internet এ পাঠানো প্রতিটা byte এর দাম। Product এর ছবি সোজা storage bucket থেকে, পুরো size এ, প্রতিটা phone এ, প্রতিবার পাঠানো হচ্ছিল। আর একটা bot OTP endpoint এ বারবার ঘা দিচ্ছিল, code আন্দাজ করছিল আর company র খরচে হাজার হাজার SMS পাঠাচ্ছিল।

যেকোনো সিরিয়াস design review তে এই দুইটা প্রশ্ন আসেই, আর জিনিস চালু করার দিকে মন থাকলে দুইটাই সহজে ভুলে যাওয়া যায়। এই chapter এ আছে কে কী করতে পারবে (authentication আর authorization), transit আর rest এ data রক্ষা, secret সামলানো, abuse ঠেকানো, আর তোমার diagram এর প্রতিটা box এর মাসিক খরচ আন্দাজ করা।

সমস্যা: চলছে, কিন্তু খোলা আর দামি

কী হয়কেন
একটা ID বদলে একজন user আরেকজনের data পড়েAPI login দেখে (authN), মালিকানা দেখে না (authZ)
Database এর password public GitHub repo তেSecret রাখা হয়েছে code বা config file এ
একটা OTP বা login endpoint brute-force হয়প্রতি account, প্রতি IP, প্রতি code এ চেষ্টার কোনো সীমা নেই
রাতারাতি SMS এর খরচ বিস্ফোরিতযে কেউ কোনো সীমা বা check ছাড়া SMS পাঠাতে পারে
Bill এর বেশিরভাগই data transferবড় file CDN এর বদলে origin থেকে, কোনো compression বা resize ছাড়া
অলস resource মাসের পর মাস টাকা খায়খরচের কোনো মালিক বা পর্যালোচনা নেই; tag নেই, budget নেই

একটা apartment building এর কথা ভাবো

ঢাকার একটা apartment building এর গেটে একজন guard আছেন। তিনি দেখেন তুমি কে: ID দেখাও, বা তিনি চেনেন। এটাই authentication: প্রমাণ করা তুমি কে।

কিন্তু building এ ঢুকতে দেওয়া মানে যেকোনো flat এ ঢুকতে পারা না। তোমার চাবি খোলে 5B, আর কিছু না। এটাই authorization: কে তুমি জানার পর তুমি কী করতে পারবে। যে building এ guard ID দেখে কিন্তু প্রতিটা চাবি প্রতিটা flat খোলে, সেটাই hook এর IDOR bug।

Building এর master চাবি গেটে টেপ দিয়ে লাগানো থাকে না; একটা তালাবন্ধ বাক্সে থাকে, কে কখন নিল তার খাতা সহ। এটা secret management। কেউ রাত ৩টায় প্রতিটা flat এর bell বাজাতে থাকলে guard তাকে ঢুকতে দেওয়া বন্ধ করেন। এটা abuse prevention। আর building এর কমিটি প্রতি মাসে বিদ্যুতের bill দেখে, সারাদিন জ্বলে থাকা বারান্দার বাতি খুঁজে বের করে timer লাগায়। এটা cost পর্যালোচনা।

TLSencrypted in transitGatewaywho are you? (authN)Orders servicemay you? (authZ)Databaseencrypted at restrahim: order 102404rahim: order 101logged in: the gateway knows this is rahimorder 102 belongs to karim: refuse (and don't reveal it exists)order 101 is rahim's own: allowedauthentication says who; authorisation must be checked for every object
Play চাপো। rahim এর request encrypted হয়ে যায়। Gateway নিশ্চিত করে এটা rahim (authentication)। Orders service দেখে order 102 তার কিনা: এটা karim এর, তাই 404 দেয়, আছে কিনা সেটাও না জানিয়ে। তার নিজের order 101 database পর্যন্ত যায়, যেটা rest এ encrypted।

তবে একটা জায়গায় মেলে না। Building এর গেট একটা। একটা application এর শত শত endpoint, আর প্রতিটাই একটা গেট, internal গুলো, background job আর admin tool সহ। Authorization প্রতিটায়, প্রতিটা object এর জন্য check করতে হয়, সামনে একবার না।

আগে অনুমান করো

Login করা একজন user GET /orders/102 চাইল। Handler দেখে একটা valid session আছে কিনা, তারপর database থেকে order 102 load করে। কী বাদ পড়েছে?

Security আর cost এর পেছনের নিয়মগুলো

  1. Authentication: কে call করছে প্রমাণ করো। User login করে password (bcrypt বা Argon2 এর মতো slow, salted hash হিসেবে রাখা), OTP, passkey বা একটা identity provider (OAuth/OpenID Connect) দিয়ে। ফলাফল একটা session cookie বা অল্প সময়ের token। Service রা একে অপরের কাছে নিজেদের প্রমাণ করে mTLS বা signed service token দিয়ে।
  2. Authorization: প্রতিটা object, প্রতিবার check করো। কে জানার পর জিজ্ঞেস করো "এই user কি এই object এ এই কাজটা করতে পারবে?", server এ, প্রতিটা request এ। Role (RBAC) বা attribute (ABAC) ব্যবহার করো, default এ না বলো, আর যেখানে object আছে সেটা নিশ্চিত করাও তথ্য ফাঁস করে সেখানে 403 এর বদলে 404 দাও।
  3. Transit আর rest এ encrypt করো। সব জায়গায় TLS, internal service এর মধ্যেও। Disk, database আর backup encrypt করো (managed service এ সাধারণত একটা setting), আর একটা key management service (KMS) ব্যবহার করো যাতে key rotate হয় আর access log থাকে।
  4. Secret code এর বাইরে রাখো। Database password, API key আর signing key থাকে একটা secret manager এ (Vault, AWS Secrets Manager, GCP Secret Manager, encryption সহ Kubernetes secret), runtime এ দেওয়া হয়, rotate করা হয়, কখনো log হয় না। Repository তে ফাঁস হওয়া secret scan করো।
  5. সবচেয়ে কম অনুমতি। প্রতিটা service, মানুষ আর machine শুধু ততটুকু permission পায় যতটুকু লাগে: reporting job একটা schema পড়ে, web app table drop করতে পারে না, intern দের production এ access নেই।
  6. Input check করো আর output encode করো। Parameterised query ব্যবহার করো (কোনো SQL injection না), edge এ size আর type check করো, XSS ঠেকাতে output encode করো, আর upload এ সীমা দাও।
  7. Edge এ abuse ঠেকাও। Login, OTP, signup আর SMS endpoint এ প্রতি account, প্রতি IP আর প্রতি device rate limit দাও; OTP দ্রুত expire করাও আর কয়েকটা ভুল চেষ্টার পর বাতিল করো; আক্রমণের সময় CAPTCHA বা proof-of-work যোগ করো; সংবেদনশীল কাজে হঠাৎ বৃদ্ধি দেখো।
  8. প্রতিটা box এর খরচ জানো। প্রতিটা component এর জন্য: compute (instance × ঘণ্টা), storage (GB-মাস), request, আর data transfer। Internet এ আর region এর মধ্যে egress প্রায়ই সবচেয়ে বড় চমক। একটা CDN, compression, ছোট করা ছবি আর caching এটা অনেক কমিয়ে দেয়।
  9. খরচ দৃশ্যমান রাখো আর তার মালিক রাখো। Team আর service ধরে resource এ tag দাও, alert সহ budget ঠিক করো, প্রতি মাসে উপরের খরচের line গুলো দেখো, রাতে অলস environment বন্ধ করো, মানানসই জায়গায় reserved বা spot capacity ব্যবহার করো। Latency র মতো খরচও design এর একটা গুণ।
from the cloudthrough a CDNegress 75,000 USDegress 18,750servers, database and storage: 2,610 USD (the thin slice on the left)egress: 75,000 of 77,610 USDsame photos via a CDN: 18,750, total 21,360 USDestimate the bill per box before you build it; the surprise is rarely the servers
Play চাপো। Photo app এর এক মাস, মাপ অনুযায়ী আঁকা। Server, database আর storage হলো বামের পাতলা অংশটা: 2,610 USD। Cloud থেকে সোজা ছবি পাঠালে যোগ হয় 75,000 USD এর egress, bill এর 96%। CDN দিয়ে একই ছবির খরচ 18,750, আর মোট নামে 21,360 এ।

নিরাপদে OTP দিয়ে একটা login

ধাপে ধাপে দেখো

User phone OTP দিয়ে login করলে কী হয়

Phone appAPI gatewayAuth serviceSMS providerSession store

ধাপ 1/5 Edge এ request check হয়

Gateway তে TLS শেষ হয়। OTP চাওয়ার endpoint এ rate limit: প্রতি phone number এ 15 মিনিটে 3টা, প্রতি IP তে ঘণ্টায় 20টা, তাই একটা bot SMS ছিটাতে পারে না।

limit
3/phone/15 মিনিট, 20/IP/ঘণ্টা
TLS
হ্যাঁ

ধাপে ধাপে একটা security আর cost review

  1. প্রতিটা প্রবেশপথের list করো: public API, admin tool, internal service, job, webhook।
  2. প্রতিটার জন্য authN আর authZ দেখো: কে call করতে পারে, আর প্রতিটা object access কি server এ check হয়?
  3. Data অনুসরণ করো: ব্যক্তিগত বা payment এর data কোথায় রাখা, পাঠানো আর log হয়? Transit আর rest এ encrypted? Log কি ফাঁস করে?
  4. Secret গুলোর list করো আর কোথায় থাকে দেখো; rotation সহ একটা secret manager এ সরাও।
  5. যে endpoint গুলোর অপব্যবহার করা যায় খুঁজে বের করো (login, OTP, signup, SMS, search, upload) আর সেগুলোয় limit দাও।
  6. প্রতিটা box এর দাম বসাও: compute, storage, request আর data transfer প্রতি মাসে, আজকের load এ আর 10 গুণে।
  7. সবচেয়ে সস্তা বড় লাভগুলো বাছো (CDN, caching, ঠিক size, অলস জিনিস বন্ধ করা) আর budget আর alert ঠিক করো।

নিজে বলো: নিরাপদ আর বুদ্ধিমান, নাকি না?

প্রতিটা case চালিয়ে দেখো

এটা কি একটা design review পার হবে?

URL এ order ID বদলালে 404 আসে
কাজ করে: ঠিক আছে। প্রতিটা object এর জন্য server এ authorization চাপানো হয়েছে, তাই ID আন্দাজ করে কিছু পাওয়া যায় না।

চালিয়ে দেখার মতো authorization, abuse আর cost

তিনটা অংশ। আরেকজনের order এর একটা request, শুধু login check দিয়ে আর মালিকানার check সহ সামলানো। একজন আক্রমণকারী ক্রমানুসারে 4 অঙ্কের OTP আন্দাজ করছে, কোনো সীমা ছাড়া আর "5 চেষ্টা, তারপর code বাতিল" সহ। আর estimation chapter এর photo app এর এক মাস, ধরে নেওয়া গোল সংখ্যা দিয়ে দাম বসানো, cloud থেকে সোজা ছবি পাঠানো বনাম CDN দিয়ে পাঠানো। সব অঙ্ক cent এ integer, তাই প্রতিটা language একই bill print করে।

package main

import (
	"fmt"
	"strings"
)

// commas writes 1234567 as 1,234,567.
func commas(n int64) string {
	s := fmt.Sprint(n)
	var b strings.Builder
	for i, c := range s {
		if i > 0 && (len(s)-i)%3 == 0 {
			b.WriteByte(',')
		}
		b.WriteRune(c)
	}
	return b.String()
}

// 1. Authorisation. Logged in is not the same as allowed: rahim asks for karim's order.
func authz() {
	owner := map[int]string{101: "rahim", 102: "karim"}
	get := func(user string, order int, checkOwner bool) string {
		if user == "" {
			return "401 log in first"
		}
		if checkOwner && owner[order] != user {
			return "404 not found" // don't even confirm the order exists
		}
		return fmt.Sprintf("200 order %d (owner %s)", order, owner[order])
	}
	fmt.Println("rahim is logged in and requests GET /orders/102 (karim's):")
	fmt.Printf("  only checks login:     %s\n", get("rahim", 102, false))
	fmt.Printf("  checks the owner too:  %s\n", get("rahim", 102, true))
}

// 2. Abuse: guessing a 4-digit OTP (10,000 codes) at 100 tries a second. The real code is
// 7351; the attacker simply tries 0000, 0001, 0002, ...
func guess(limit int) (tries int, found bool) {
	const secret = 7351
	for code := 0; code < 10_000; code++ {
		if limit > 0 && tries == limit {
			return tries, false // the code is burned: a new one must be requested
		}
		tries++
		if code == secret {
			return tries, true
		}
	}
	return tries, false
}

func otp() {
	fmt.Println("4-digit OTP, attacker tries 100 codes a second:")
	tries, found := guess(0)
	fmt.Printf("  no limit:                    found=%t after %d tries (%d s)\n", found, tries, tries/100)
	tries, found = guess(5)
	fmt.Printf("  5 tries, then the code dies: found=%t after %d tries; odds per code sent: 1 in %d\n", found, tries, 10_000/5)
}

// 3. Cost of the photo app from the estimation chapter, one month. Prices are assumed round
// numbers, in US cents, to compare options; check your provider's real prices.
func cost() {
	const (
		servers       = 5 * 70_00  // 5 app servers at $70
		database      = 2 * 400_00 // primary + standby at $400
		storage       = 73 * 20_00 // 73 TB stored after year one, $20 per TB
		egressGB      = 1_500_000  // 100M views a day x 500 KB x 30 days = 1,500 TB
		directPerGB   = 5          // cents per GB straight from the cloud
		cdnPerGB      = 1          // cents per GB from a CDN
		originShareOf = 5          // % of CDN traffic that still comes from the origin
	)
	direct := int64(egressGB * directPerGB)
	viaCDN := int64(egressGB*cdnPerGB + egressGB*originShareOf/100*directPerGB)
	base := int64(servers + database + storage)
	fmt.Println("one month of the photo app (assumed prices):")
	fmt.Printf("  servers $%s, database $%s, storage $%s\n", commas(servers/100), commas(database/100), commas(storage/100))
	fmt.Printf("  serving photos from the cloud directly: $%s egress, $%s in total (egress %d%%)\n",
		commas(direct/100), commas((base+direct)/100), direct*100/(base+direct))
	fmt.Printf("  serving photos through a CDN:           $%s egress, $%s in total\n", commas(viaCDN/100), commas((base+viaCDN)/100))
}

func main() {
	authz()
	otp()
	cost()
}

ছয়টাই হুবহু এটা print করে:

rahim is logged in and requests GET /orders/102 (karim's):
  only checks login:     200 order 102 (owner karim)
  checks the owner too:  404 not found
4-digit OTP, attacker tries 100 codes a second:
  no limit:                    found=true after 7352 tries (73 s)
  5 tries, then the code dies: found=false after 5 tries; odds per code sent: 1 in 2000
one month of the photo app (assumed prices):
  servers $350, database $800, storage $1,460
  serving photos from the cloud directly: $75,000 egress, $77,610 in total (egress 96%)
  serving photos through a CDN:           $18,750 egress, $21,360 in total

Output টা পড়ো:

  • only checks login: rahim karim এর order পেয়ে যায়। Login করা থাকাকে অনুমতি থাকা ধরে নেওয়া হয়েছে।
  • checks the owner too: 404। Server object টা check করেছে, আর order 102 যে আছে সেটাও নিশ্চিত করেনি।
  • no limit: আক্রমণকারী 7,352টা চেষ্টায় code পেয়ে গেছে, সেকেন্ডে 100টা চেষ্টায় 73 সেকেন্ডে।
  • 5 tries, then the code dies: প্রতি পাঠানো code এ আক্রমণকারী পায় 5টা আন্দাজ, 2,000 এ 1 সুযোগ, আর প্রতিটা নতুন code মানে আরেকটা SMS, যেটা তোমার limit আর alarm দেখতে পায়।
  • cloud থেকে খরচ: server, database আর storage 2,610 USD; egress 75,000 USD, bill এর 96%।
  • CDN দিয়ে খরচ: 18,750 USD এর transfer (CDN delivery আর origin থেকে এখনো আসা 5%), মোট 21,360 USD। দামগুলো ধরে নেওয়া; মনে রাখার জিনিস হলো আকৃতিটা (media নির্ভর app এ transfer ই প্রধান)।
  • কোন language এ কী বদলালো: boolean আর টাকার format। Go %t দিয়ে true print করে, Java %b দিয়ে, Rust আর TypeScript সরাসরি true, Python এ লাগে str(found).lower() কারণ সে True লিখত, আর C++ শব্দটা নিজে বাছে। হাজারের comma আসে Python এর :, থেকে; বাকিরা ছোট একটা commas helper ব্যবহার করে যাতে output machine এর locale এর উপর নির্ভর না করে।

আসল project এ security আর cost

  • প্রমাণিত জিনিস ব্যবহার করো: একটা identity provider বা ভালোভাবে test করা auth library, framework এর CSRF আর session সামলানো, আর তোমার cloud এর KMS আর secret manager। নিজের crypto লিখবে না।
  • Authorization এক জায়গায়: একটা policy layer বা helper যেটা প্রতিটা handler call করে (বা OPA/Cedar এর মতো policy engine), সাথে এমন test যেগুলো অন্য user এর object access করার চেষ্টা করে। নতুন endpoint এ এটা review করো।
  • Security header আর TLS: HSTS, একটা Content Security Policy, Secure/HttpOnly/SameSite cookie, আর automatic certificate renewal।
  • Dependency আর image: জানা দুর্বলতার জন্য scan করো, version pin করো, নিয়মিত rebuild করো, আর base image ছোট রাখো।
  • Audit আর alert: admin এর কাজ আর permission বদল log করো, অস্বাভাবিক spike এ alert দাও (login, OTP request, export), আর একটা incident response plan রাখো।
  • খরচের অভ্যাস: প্রতিটা resource এ tag, প্রতি team budget আর alert, প্রতি মাসে উপরের 10টা line পর্যালোচনা, আসল ব্যবহার দেখে ঠিক size, autoscaling আর schedule, storage এর lifecycle rule, আর বড় ও public যেকোনো কিছুর জন্য CDN। QPS যেমন আন্দাজ করেছিলে, design এর সময়েই bill আন্দাজ করো।

যে design গুলো পরে problem create করে

প্রতিটা case চালিয়ে দেখো

Security বা cost এর ভুলটা ধরো

আরেকটা breach থেকে পাওয়া পুরনো password দিয়েই খুলে গেল
ভাঙে: Admin tool রাখো MFA সহ SSO, একটা VPN বা identity-aware proxy র পেছনে, আর প্রতিটা কাজ log করো।

একটু থামো, ভাবো

তোমার app মাসে 1,500 TB ছবি পাঠায় আর bill বলছে 75,000 USD egress। একটা CDN এর খরচ হবে মোটামুটি 15,000 USD আর কিছু origin traffic। একজন teammate বলল "ছবিগুলো WebP তে convert করে phone এর জন্য ছোটও করি; এখন 500 KB, তখন হবে মোটামুটি 150 KB"। Bill এর কী হবে, আর কোন change আগে করা উচিত?

উত্তর দেখো

Resize আর আধুনিক format তুমি যেখান থেকেই পাঠাও প্রতিটা byte ছোট করে। 500 KB থেকে 150 KB মানে transfer নেমে আসে 30% এ: 1,500 TB হয় মোটামুটি 450 TB। প্রতি GB প্রায় 1 cent এ CDN দিয়ে সেটা মোটামুটি 4,500 USD আর ছোট একটা origin এর ভাগ, 18,750 এর বদলে। CDN ছাড়াও 5 cent এ 450 TB হয় প্রায় 22,500 USD, 75,000 এর বদলে। দুইটা change ই করার মতো। ছবির change টা সাধারণত আগে, কারণ এটা slow mobile network এ page ও দ্রুত load করায়, আর CDN এর সাশ্রয়ের সাথে গুণ হয়: ছোট file edge থেকে এলেও সস্তা, origin থেকে এলেও সস্তা।

ছোট্ট quiz

1. Authentication আর authorization এর পার্থক্য কী?

উত্তর

Authentication ঠিক করে caller কে (login, token)। Authorization ঠিক করে ওই caller এই নির্দিষ্ট object এ এই কাজটা করতে পারবে কিনা, আর প্রতিটা request এ server এ check করতে হয়।

2. Database password এর মতো secret কোথায় থাকা উচিত?

উত্তর

একটা secret manager এ, runtime এ দেওয়া, access control, audit আর rotation সহ। কখনো code, image, Git এর config file বা log এ না।

3. একটা OTP endpoint কে brute force আর SMS এর অপব্যবহার থেকে কীভাবে বাঁচাবে?

উত্তর

যথেষ্ট অঙ্ক, ছোট মেয়াদ, প্রতি code এ কয়েকটা চেষ্টা (তারপর বাতিল), phone number, IP আর device ধরে rate limit, আক্রমণের সময় CAPTCHA, আর অস্বাভাবিক SMS সংখ্যা বা খরচে alert।

4. Data transfer প্রায়ই খরচের সবচেয়ে বড় চমক কেন?

উত্তর

এটা প্রতিটা user আর পাঠানো প্রতিটা byte এর সাথে বাড়ে, আর server আন্দাজের সময় সহজেই চোখ এড়ায়। Origin থেকে বড় media, cross-region replication আর zone জুড়ে বেশি কথা বলা service সবই transfer এর charge যোগ করে। CDN, compression, resize আর caching এটা কমায়।

তোমার challenge: CDN এর ভালো hit ratio

Program ধরে নেয় CDN traffic এর 5% এখনো origin থেকে আসে (95% cache hit ratio)। ভালো cache header আর version দেওয়া file name দিয়ে তুমি পৌঁছালে 98% এ। Origin এর ভাগ 5 থেকে বদলে 2 করো আর আবার চালাও।

  • শুধু ওই একটা সংখ্যা বদলাও।
  • আগে আর পরে CDN এর line টা দেখো।

তুমি পেরেছো যখন CDN এর line টা হবে serving photos through a CDN: $16,500 egress, $19,110 in total। Hit ratio র তিন percentage point মাসে 2,250 USD বাঁচাল, কারণ প্রতিটা miss এর bill হয় দামি origin এর হারে। Cache header গতির সিদ্ধান্ত যতটা, খরচের সিদ্ধান্তও ততটা।

এক লাইনে

প্রতিটা caller কে authenticate আর প্রতিটা object কে server এ authorize করো, transit আর rest এ encrypt করো, secret রাখো একটা manager এ আর অনুমতি দাও সবচেয়ে কম, আক্রমণকারী যা ছিটাতে পারে তাতে rate limit আর মেয়াদ দাও, আর বানানোর আগে diagram এর প্রতিটা box এর দাম বসাও (বিশেষ করে data transfer), তারপর অন্য যেকোনো metric এর মতো bill এ tag, budget আর পর্যালোচনা চালাও।

Station stamp

Chapter শেষ করো

পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।

Pro নিয়ে XP পাও →