এই পর্বে যা শিখবেন
- CIA triad, trust boundary আর data-flow diagram ধরে STRIDE-এ threat model বানানো
- Defence in depth, least privilege আর zero trust — নীতি থেকে বাস্তব নকশায়
- Password সংরক্ষণের খরচের অঙ্ক, MFA, passkey, আর JWT যাচাইয়ের পূর্ণ তালিকা
- RBAC, ABAC, ReBAC আর ownership check — একটি policy evaluator চালিয়ে
- OAuth 2.0-এর authorization code + PKCE আর OIDC ধাপে ধাপে; envelope encryption, key rotation আর secrets
১ · নিরাপত্তা একটি নকশার গুণ — CIA triad আর threat modeling
একটি ব্যাংকের শাখার কথা ভাবুন। দরজায় গার্ড, কাউন্টারে ক্যাশিয়ার পরিচয়পত্র দেখেন, ভল্টের চাবি ম্যানেজারের কাছে, আর প্রতিটি লেনদেন খাতায় ওঠে। ভবন তৈরির পরে “একটা ভল্ট বসিয়ে দেব” চলে না — ভল্টের দেয়াল আর টাকার পথ শুরুর নকশাতেই ঠিক হয়। সফটওয়্যারেও তাই: কোন তথ্য কোথায় থাকবে, কে কোন পথে ঢুকবে, কোন সার্ভিস কাকে বিশ্বাস করবে — এগুলো architecture-এর সিদ্ধান্ত।
নিরাপত্তা আসলে তিনটি জিনিস রক্ষা করে, যাদের একসাথে বলে CIA triad:
🔒 Confidentiality — গোপনীয়তা
যার দেখার অধিকার নেই, তিনি দেখবেন না: রিনার অর্ডার কারিম দেখতে পাবেন না, log-এ password থাকবে না।
হাতিয়ার: authorization, encryption✅ Integrity — অখণ্ডতা
অনুমতি ছাড়া কেউ বদলাতে পারবেন না, আর বদলালে ধরা পড়বে: পথে দাম বদলানো, token-এর ভূমিকা বদলানো।
হাতিয়ার: স্বাক্ষর, MAC, TLS, audit⚡ Availability — প্রাপ্যতা
বৈধ ব্যবহারকারী দরকারের সময় সেবা পাবেন: ফল প্রকাশের দিন ভিড়ে বা ইচ্ছাকৃত চাপে login অচল হবে না।
হাতিয়ার: rate limit, redundancy, backupসাথে আরও দুটি গুণ প্রায়ই লাগে: authenticity (যে দাবি করছে, সে-ই কি না) আর non-repudiation (করা কাজ পরে অস্বীকার করা যায় না)। CIA আর নেটওয়ার্কের দিক থেকে AAA-র বিস্তারিত আলোচনা Computer Networks কোর্সের নেটওয়ার্ক নিরাপত্তার ভিত্তি-তে আছে; এখানে আমরা দেখব নকশার টেবিলে এগুলো কীভাবে সিদ্ধান্তে বদলায়।
Threat modeling — চারটি প্রশ্ন
Threat modeling মানে নকশা থাকতেই কাঠামো মেনে ঝুঁকি খোঁজা। Adam Shostack-এর জনপ্রিয় করা কাঠামোয় চারটি প্রশ্ন: (১) আমরা কী বানাচ্ছি? (২) কী ভুল হতে পারে? (৩) তা নিয়ে কী করব? (৪) কাজটি কি ঠিকমতো হলো? প্রথম প্রশ্নের উত্তর হলো একটি data-flow diagram (DFD): কোন প্রক্রিয়া, কোন data store, কোন বাইরের পক্ষ, আর তাদের মাঝে কোন তীরে ডেটা চলে। তারপর আঁকুন trust boundary — যেখানে বিশ্বাসের মাত্রা বদলায়: ইন্টারনেট থেকে আপনার edge, edge থেকে ভেতরের নেটওয়ার্ক, একটি সার্ভিস থেকে database, এক tenant থেকে আরেক tenant. ঝুঁকি জন্মায় প্রায় সবসময় এই সীমানাগুলোতে।
দ্বিতীয় প্রশ্নের জন্য একটি চেকলিস্ট STRIDE — ১৯৯৯ সালের দিকে Microsoft-এ Loren Kohnfelder আর Praerit Garg-এর প্রস্তাব বলে পরিচিত। ছয়টি অক্ষর ছয় রকম ঝুঁকি, আর প্রতিটির বিপরীতে একটি রক্ষার গুণ। নিচে একটি কাল্পনিক অনলাইন বাজারের ছোট DFD — ব্রাউজার, gateway, Orders service আর database — ধরে ছয় অক্ষর একে একে প্রয়োগ করুন:
- ব্রাউজার
বাইরের পক্ষঅবিশ্বস্ত - API gateway
সীমানা ১ পেরিয়েপ্রথম যাচাই - Orders service
প্রক্রিয়াrefund, অর্ডার - Database
data storeঅর্ডার, log
| STRIDE-এর অক্ষর | |
| DFD-র কোথায় | |
| কী ভুল হতে পারে | |
| নকশায় প্রতিরক্ষা | |
| রক্ষিত গুণ |
- S — পরিচয় জাল। প্রথম trust boundary-তে প্রশ্ন: যিনি “আমি রিনা” বলছেন, তিনি কি সত্যিই রিনা? Password ফাঁস বা চুরি যাওয়া token-ই এখানে সাধারণ পথ। প্রতিরক্ষা অংশ ৩ আর ৪-এ: ধীর hash, দ্বিতীয় factor, ছোট মেয়াদ।
- T — বদলে দেওয়া। তীর ধরে চলা ডেটা পথে বদলাতে পারে, আবার client নিজেই অনুরোধে দাম ১,২০০ থেকে ১২ লিখে পাঠাতে পারে। পথের জন্য TLS, token-এর জন্য স্বাক্ষর, আর দামের মতো তথ্য client থেকে না নিয়ে সার্ভারে খোঁজা।
- R — অস্বীকার। একজন support কর্মী বলছেন তিনি refund করেননি। কে, কখন, কোন অনুরোধ-ID-তে করেছেন — তা লেখা না থাকলে তর্কের মীমাংসা নেই। তাই সংবেদনশীল কাজে append-only audit log (অংশ ৮)।
- I — তথ্য ফাঁস। Data store, log আর error-বার্তা — তিন জায়গাতেই ফাঁস হয়। অন্যের অর্ডার দেখা (ownership check নেই), backup-এর ডিস্ক হারানো (at-rest encryption নেই), stack trace-এ ভেতরের ঠিকানা, log-এ token — সবই এই অক্ষরে।
- D — অচল করা। Login আর search-এর মতো দামি endpoint-ই লক্ষ্য। প্রতিরক্ষা অন্য পর্বে: edge-এ WAF আর CDN (পর্ব ১৭), load shedding (পর্ব ১৮), rate limit (পর্ব ৪৯), timeout (পর্ব ৪৮)।
- E — অধিকারের বাইরে যাওয়া। গ্রাহক কোনোভাবে support-এর refund endpoint ডেকে ফেললেন, অথবা gateway পেরিয়ে সরাসরি ভেতরের সার্ভিসে পৌঁছালেন। প্রতিরক্ষা: সব কিছু ডিফল্টে নিষেধ (deny by default), প্রতিটি সার্ভিসে আলাদা authorization, আর database-এ সীমিত অধিকারের user.
প্রতিটি অক্ষরের উত্তর একটি নকশার সিদ্ধান্ত — কোথায় যাচাই, কোথায় log, কোন চাবি কোথায়। STRIDE একমাত্র কাঠামো নয়; আসল কথা হলো প্রতিটি নতুন feature বা নতুন trust boundary এলেই প্রশ্নগুলো আবার করা।
২ · নকশার নীতি — defence in depth, least privilege আর zero trust
১৯৭৫ সালে Jerome Saltzer আর Michael Schroeder তাঁদের বিখ্যাত প্রবন্ধে কয়েকটি নকশা-নীতি লেখেন, যা আজও প্রায় হুবহু খাটে। তিনটি এই পর্বের মেরুদণ্ড:
- Defence in depth — স্তরে স্তরে প্রতিরক্ষা। একটি স্তর ভাঙলেও পরেরটি থামাবে। WAF একটি ক্ষতিকর অনুরোধ না চিনলেও gateway token যাচাই করবে, সার্ভিস ownership দেখবে, database-এর user-এর অধিকার সীমিত, আর ডিস্কের ডেটা এনক্রিপ্ট করা।
- Least privilege — যতটুকু দরকার, ততটুকুই। Reports service শুধু পড়তে পারে, লিখতে নয়; support কর্মী শুধু নিজের অঞ্চলের অর্ডার; CI pipeline শুধু একটি registry-তে push. ভুল বা চুরি হলেও ক্ষতির পরিধি — blast radius — ছোট থাকে।
- Complete mediation — প্রতিটি প্রবেশে যাচাই। আগের একবারের যাচাইয়ের ফল ধরে না রেখে প্রতিটি অনুরোধে অনুমতি দেখা। এরই আধুনিক রূপ zero trust.
পুরোনো নকশা ছিল দুর্গের মতো: বাইরে শক্ত দেয়াল (firewall, VPN), ভেতরে সবাই বিশ্বস্ত — একটি চুরি যাওয়া ল্যাপটপ বা দুর্বল সার্ভিস দিয়ে ঢুকলেই সব খোলা। Zero trust উল্টো কথা বলে: নেটওয়ার্কের কোথায় আছেন, তা দিয়ে বিশ্বাস নয়; প্রতিটি অনুরোধে পরিচয় (মানুষ বা সার্ভিস), ডিভাইসের অবস্থা আর অনুমতি যাচাই, আর ভেতরের traffic-ও এনক্রিপ্ট। মার্কিন NIST-এর SP 800-207 (২০২০) এই ধারণাকে একটি architecture হিসেবে লিখেছে; তবে zero trust কোনো একটি পণ্য নয়, ধীরে ধীরে নেওয়া একটি নকশা-দিক। Segmentation আর VPN-এর দিক থেকে আলোচনা CN কোর্সের segmentation আর zero trust অংশে।
Trust boundary — একটি অনুরোধ কোন কোন সীমানা পেরোয়
Browser → edge (CDN/WAF) → API gateway → services (mTLS) → database · KMS/vault · audit log
Edge আর gateway প্রথম ছাঁকনি — তাদের কাজের ভাগ পর্ব ১১-এ। কিন্তু Orders service gateway-কে অন্ধভাবে বিশ্বাস করে না: কে ডাকছে তা mTLS-এর certificate-এ দেখে, আর ব্যবহারকারী এই অর্ডারের মালিক কি না নিজে মেলায়। Database-এর user শুধু দরকারি কাজ পারে; চাবি থাকে আলাদা ঘরে, আর তার প্রতিটি ব্যবহার audit log-এ ওঠে।
“ভেতরের header”-এর ফাঁদ। অনেক নকশায় gateway token যাচাই করে ভেতরে X-User-Id: 1042 পাঠায়, আর সার্ভিস সেটি মেনে নেয়। Gateway পাশ কাটিয়ে কেউ যদি ভেতরের নেটওয়ার্ক থেকে সরাসরি সার্ভিসে পৌঁছায়, সে নিজেই যেকোনো ID লিখতে পারে। প্রতিকার: সার্ভিস শুধু mTLS-এ পরিচিত gateway থেকে এমন header নেবে, অথবা gateway-এর সই করা স্বল্পমেয়াদি token পাঠাবে যা সার্ভিস নিজে যাচাই করে।
৩ · Authentication — password ঠিকভাবে রাখা, MFA আর passkey
Authentication প্রমাণ করে “আপনি কে”; authorization ঠিক করে “আপনি এটি করতে পারেন কি না”। HTTP-তে প্রথমটি ব্যর্থ হলে 401, দ্বিতীয়টি হলে 403 — পার্থক্যটি পর্ব ৮-এ দেখেছেন। পরিচয়ের প্রমাণ তিন রকম: যা আপনি জানেন (password), যা আপনার কাছে আছে (ফোন, security key), আর যা আপনি (আঙুলের ছাপ, মুখ)।
Password রাখার নিয়ম — কেন “ধীর” hash
সার্ভার কখনো password নিজে রাখবে না, এমনকি এনক্রিপ্ট করেও নয় — কারণ চাবি থাকলে উল্টো খোলা যায়। রাখবে একটি একমুখী password hash. কিন্তু SHA-256-এর মতো সাধারণ hash এখানে ভুল হাতিয়ার: এগুলো দ্রুত হওয়ার জন্যই বানানো, আর database চুরি হলে আক্রমণকারী একটি GPU-তে সেকেন্ডে বিলিয়ন অনুমান যাচাই করতে পারেন। তাই password-এর জন্য বানানো ইচ্ছাকৃত ধীর ফাংশন — Argon2id, bcrypt বা scrypt — যাদের কাজের পরিমাণ (work factor) আর Argon2id-এ memory-র পরিমাণ বাড়ানো যায়। Memory-নির্ভর বলে GPU-তে এদের সমান্তরালে চালানো অনেক কঠিন।
সাথে প্রতিটি ব্যবহারকারীর জন্য আলাদা এলোমেলো salt, hash-এর পাশে খোলাখুলি রাখা। Salt না থাকলে একই password-এর hash সবার জন্য এক — একটি অনুমান একসাথে লাখো মানুষের সাথে মেলানো যায়, আর আগে থেকে বানানো টেবিলও চলে। চাইলে একটি pepper-ও: সবার জন্য একটি গোপন চাবি, যা database-এ নয়, vault বা HSM-এ থাকে। লেখার সময়ে OWASP-এর Password Storage Cheat Sheet প্রথম পছন্দ হিসেবে Argon2id সুপারিশ করে (ন্যূনতম উদাহরণ হিসেবে প্রায় ১৯ MiB memory, ২ বার পুনরাবৃত্তি); না পেলে scrypt, পুরোনো ব্যবস্থায় bcrypt (cost অন্তত ১০; bcrypt input-এর প্রথম ৭২ byte-ই শুধু ব্যবহার করে, বাকিটা বাদ পড়ে), আর FIPS-এর বাধ্যবাধকতা থাকলে PBKDF2 —সংখ্যাগুলো সময়ের সাথে বদলায়, তাই নিজের সার্ভারে মেপে বেছে নিন। Hash-এর সাথে parameter-ও লেখা থাকে, তাই পরে শক্ত parameter-এ সরলে পরের login-এ নতুন করে hash করা যায়।
কত ধীর যথেষ্ট? নিচের হিসাব শুধু পাটিগণিত: প্রতিটি সম্ভাব্য password একবার করে চেষ্টা করতে কত সময় লাগে, ধরা যাক একটি GPU আর একজন ব্যবহারকারীর salt দেওয়া hash. সব হার ধরা যাক — মাত্রা বোঝার জন্য, নির্দিষ্ট হার্ডওয়্যারের মাপ নয়:
// Password storage cost model: pure arithmetic, no cracking code. Every rate is an ASSUMPTION (ধরা যাক).
// guesses/s = one attacker GPU testing candidates against ONE stolen, salted hash
const hashes = [ // name, guesses per second (assumed), server ms per login check (assumed)
["SHA-256 x1", 1e10, 0.001], // fast hash: the WRONG tool for passwords
["bcrypt-10", 1e4, 60],
["bcrypt-12", 2.5e3, 240], // +1 cost = 2x work, so cost 12 = 4x cost 10
["Argon2id-19M", 1e3, 50], // memory-hard: GPUs gain much less
];
const spaces = [ // label, how many possible passwords (worst case: attacker tries all)
["8 lowercase", 26 ** 8], ["8 a-zA-Z0-9", 62 ** 8],
["12 a-zA-Z0-9", 62 ** 12], ["4 random words", 7776 ** 4],
];
function human(sec) {
for (const [u, s] of [["yr", 31557600], ["day", 86400], ["hr", 3600], ["min", 60], ["s", 1]])
if (sec >= s) { const v = sec / s;
return (v >= 1e5 ? v.toExponential(1) : v.toFixed(v < 10 ? 1 : 0)) + " " + u; }
return sec.toExponential(1) + " s";
}
console.log("Time to try the WHOLE space (1 GPU, 1 user's salted hash)");
console.log("space".padEnd(15) + "size".padStart(9) + hashes.map(h => h[0].padStart(13)).join(""));
for (const [label, n] of spaces)
console.log(label.padEnd(15) + n.toExponential(1).padStart(9) + hashes.map(h => human(n / h[1]).padStart(13)).join(""));
console.log("\nServer side: login checks per second on ONE CPU core");
for (const [name, , ms] of hashes) console.log(" " + name.padEnd(14) + String(Math.floor(1000 / ms)).padStart(8) + " /s");
const users = 1e6; // a stolen table of one million users
console.log("\nNo salt : each guess is compared with all " + users.toExponential(0) + " hashes at once." +
"\nUnique salt: the same work must be repeated for every user.");আউটপুট:
Time to try the WHOLE space (1 GPU, 1 user's salted hash)
space size SHA-256 x1 bcrypt-10 bcrypt-12 Argon2id-19M
8 lowercase 2.1e+11 21 s 242 day 2.6 yr 6.6 yr
8 a-zA-Z0-9 2.2e+14 6.1 hr 692 yr 2768 yr 6919 yr
12 a-zA-Z0-9 3.2e+21 10223 yr 1.0e+10 yr 4.1e+10 yr 1.0e+11 yr
4 random words 3.7e+15 4.2 day 11586 yr 46343 yr 1.2e+5 yr
Server side: login checks per second on ONE CPU core
SHA-256 x1 1000000 /s
bcrypt-10 16 /s
bcrypt-12 4 /s
Argon2id-19M 20 /s
No salt : each guess is compared with all 1e+6 hashes at once.
Unique salt: the same work must be repeated for every user.
প্রথম সারি সবচেয়ে শিক্ষণীয়: ৮ অক্ষরের ছোট হাতের password-এর পুরো জগৎ (২.১ × ১০১১) fast hash-এ 21 s-এ শেষ, অথচ bcrypt-10-এ 242 day আর Argon2id-এ 6.6 yr — একই password, শুধু সংরক্ষণের ধরন বদলে প্রায় এক কোটি গুণ বাড়তি খরচ। ১২ অক্ষরের মিশ্র password বা চারটি এলোমেলো শব্দ (৭,৭৭৬ শব্দের তালিকা থেকে) ধীর hash-এ দশ হাজার থেকে হাজার কোটি বছরেরও বেশি। দুটি সতর্কতা। এক, একশো GPU মানে সময় একশো ভাগের এক, আর মানুষ এলোমেলো password বাছেন না — প্রচলিত password-এর তালিকায় অনেকগুলো মিনিটেই মেলে। দুই, দাম সার্ভারও দেয়: এক core-এ সেকেন্ডে মাত্র 16 (bcrypt-10) বা 4 (bcrypt-12)টি যাচাই। তাই login-এর আলাদা worker pool আর rate limit (পর্ব ৪৯), যাতে login-এর ঢলে CPU শেষ না হয়। শেষ লাইন salt-এর যুক্তি।
Password-এর বাইরে — MFA আর passkey
Password যত ভালোভাবেই রাখুন, ব্যবহারকারী একই password অন্য সাইটে ব্যবহার করলে, বা প্রতারণামূলক পাতায় লিখে ফেললে, তা হাতছাড়া হয়। তাই দ্বিতীয় প্রমাণ — MFA (multi-factor authentication): SMS-এর OTP (সহজ, কিন্তু SIM বদলের প্রতারণায় দুর্বল), authenticator অ্যাপের TOTP (RFC 6238), বা hardware security key. NIST SP 800-63B-এর মতো নির্দেশিকা password-এর ক্ষেত্রেও কয়েকটি নকশার নিয়ম দেয়: জটিলতার জোরাজুরির চেয়ে দৈর্ঘ্য, নিয়ম করে জোর করে বদলানো নয়, আর ফাঁস হওয়া password-এর তালিকার সাথে মিলিয়ে দেখা।
সবচেয়ে শক্ত পথ passkey, যা W3C-এর WebAuthn মান (Level 1 সুপারিশ ২০১৯) আর FIDO2-এর ওপর দাঁড়িয়ে। ধারণাটি সরল: নিবন্ধনের সময় ডিভাইস একটি চাবি-জোড়া বানায়; private key ডিভাইসেই থাকে (আঙুলের ছাপ বা PIN দিয়ে খোলে), সার্ভার পায় শুধু public key. Login-এ সার্ভার একটি এলোমেলো challenge পাঠায়, ডিভাইস সই করে, সার্ভার মেলায়। সার্ভারে চুরি করার মতো গোপন কিছু নেই, আর স্বাক্ষর ওয়েবসাইটের domain-এ বাঁধা — নকল পাতায় কাজই করে না; তাই একে phishing-প্রতিরোধী বলা হয়। ফোন হারালে account ফেরানোর পথ (recovery) প্রায়ই সবচেয়ে দুর্বল জায়গা — সেটিও সমান যত্নে নকশা করুন। OS-এর দিক থেকে TOTP আর passkey-র বিস্তার OS কোর্সের password, MFA আর passkey অংশে।
৪ · Session বনাম token, আর JWT যাচাইয়ের নিয়ম
Login সফল হলে পরের প্রতিটি অনুরোধে আবার password চাওয়া যায় না; তাই সার্ভার একটি প্রমাণপত্র দেয়। দুই পথ — ব্রাউজারে একটি এলোমেলো session ID (তথ্য সার্ভারের store-এ), অথবা স্বাক্ষর করা JWT (তথ্য token-এর ভেতরে)। Cookie-র HttpOnly, Secure, SameSite flag, JWT-এর গঠন, HMAC বনাম asymmetric স্বাক্ষর আর logout-এর revocation সমস্যা পর্ব ১৫-এ বিস্তারিত — সেখানে নিজে HMAC-SHA256 লিখে test vector-ও মেলানো হয়েছে। এখানে আমরা নিরাপত্তার নকশার দিক থেকে একটি প্রশ্নে মন দেব: বহু সার্ভিসের সিস্টেমে একটি token গ্রহণের আগে যাচাইকারী ঠিক কী কী দেখবে?
| যাচাই | কেন | না থাকলে কী ঝুঁকি |
|---|---|---|
| Algorithm-এর allow-list, চাবির সাথে বাঁধা | Header-এর alg token-এর পাঠকের লেখা — বিশ্বাসযোগ্য নয় | none মেনে নেওয়া; অথবা public key-কে HMAC-এর গোপন চাবি ভেবে যাচাই — “algorithm confusion” |
kid শুধু নিজের বিশ্বস্ত তালিকায় খোঁজা | চাবি বদলের (rotation) সময় একাধিক চাবি চলে | Token-এর দেখানো অজানা ঠিকানা থেকে চাবি আনা |
| স্বাক্ষর | অখণ্ডতা: কেউ payload বদলায়নি | যে কেউ নিজের মতো scope বা ভূমিকা লেখেন |
iss | প্রত্যাশিত ইস্যুকারী | অন্য ব্যবস্থার token এখানে চলে |
aud | Token এই সার্ভিসের জন্যই | Payments-এর জন্য দেওয়া token দিয়ে Orders-এ কাজ |
exp, nbf, ছোট leeway | মেয়াদ; ঘড়ির সামান্য গরমিল মেনে | মেয়াদোত্তীর্ণ বা চুরি যাওয়া token অনির্দিষ্টকাল চলে |
scope আর ownership | এই কাজের, এই বস্তুর অনুমতি (অংশ ৫) | বৈধ token দিয়ে অন্যের ডেটা |
নিচের কোডে একটি খেলনা token — স্বাক্ষরের জায়গায় FNV-1a দিয়ে বানানো একটি “MAC”, যা আসল cryptography নয়, শুধু যাচাইয়ের ক্রম দেখানোর জন্য। আসল কাজে পরীক্ষিত library আর KMS বা vault-এ রাখা চাবি:
// Toy signed token to show the CHECKS a verifier must make. The "MAC" below is FNV-1a: NOT real crypto.
// Real systems: a tested JWT library, HS256/ES256 keys from a KMS or vault (see lesson 15 for real HMAC).
function fnv(s) { let h = 0x811c9dc5; for (const ch of s) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; } return h; }
const toyMac = (key, msg) => fnv(key + fnv(key + msg).toString(36)).toString(36); // NOT secure
const b64 = o => btoa(JSON.stringify(o)), unb64 = s => JSON.parse(atob(s));
const KEYS = { "k-2026-10": "demo-key-A" }; // kid -> key; the verifier owns this list
const sign = (h, p, key) => { const hp = b64(h) + "." + b64(p); return hp + "." + toyMac(key, hp); };
function verify(tok, now, expect) {
const [h64, p64, sig] = tok.split("."), h = unb64(h64), p = unb64(p64);
if (h.alg !== "HS256") return "REJECT alg " + h.alg + " not on allow-list";
const key = KEYS[h.kid]; if (!key) return "REJECT unknown kid";
if (toyMac(key, h64 + "." + p64) !== sig) return "REJECT bad signature";
if (p.iss !== expect.iss) return "REJECT wrong issuer " + p.iss;
if (p.aud !== expect.aud) return "REJECT token is for " + p.aud + ", not " + expect.aud;
const LEEWAY = 60; // seconds of allowed clock skew
if (now > p.exp + LEEWAY) return "REJECT expired " + (now - p.exp) + " s ago";
if (now + LEEWAY < p.nbf) return "REJECT not valid yet";
return "ACCEPT sub=" + p.sub + " scope=" + p.scope;
}
const T0 = 1793000000, H = { alg: "HS256", kid: "k-2026-10" };
const base = { iss: "https://id.example", sub: "u-1042", aud: "orders", scope: "orders.read", nbf: T0, exp: T0 + 600 };
const good = sign(H, base, "demo-key-A");
const [gh, , gs] = good.split(".");
const cases = [
["valid, 2 min later", good, T0 + 120],
["payload edited: scope", gh + "." + b64({ ...base, scope: "orders.refund" }) + "." + gs, T0 + 120],
["header says alg none", b64({ alg: "none", kid: "k-2026-10" }) + "." + good.split(".")[1] + ".", T0 + 120],
["token for payments", sign(H, { ...base, aud: "payments" }, "demo-key-A"), T0 + 120],
["retired key k-2026-04", sign({ alg: "HS256", kid: "k-2026-04" }, base, "old-key"), T0 + 120],
["30 s past exp (skew)", good, T0 + 630],
["90 s past exp", good, T0 + 690],
];
for (const [label, tok, now] of cases) console.log(label.padEnd(23) + verify(tok, now, { iss: "https://id.example", aud: "orders" }));আউটপুট:
valid, 2 min later ACCEPT sub=u-1042 scope=orders.read
payload edited: scope REJECT bad signature
header says alg none REJECT alg none not on allow-list
token for payments REJECT token is for payments, not orders
retired key k-2026-04 REJECT unknown kid
30 s past exp (skew) ACCEPT sub=u-1042 scope=orders.read
90 s past exp REJECT expired 90 s ago
(১) সব ঠিক — ACCEPT. (২) মাঝের অংশে scope বদলে orders.refund, স্বাক্ষর আগেরটিই — bad signature. (৩) Header-এ alg: none: যাচাইকারী header-কে নয়, নিজের তালিকাকে মানে — not on allow-list. (৪) সঠিক স্বাক্ষর, কিন্তু aud payments — এই সার্ভিসের জন্য নয়। বহু সার্ভিস একই চাবিতে যাচাই করলে aud না দেখাই সবচেয়ে সাধারণ ফাঁক। (৫) অবসরে যাওয়া চাবির kid — unknown kid. (৬) মেয়াদ পেরিয়েছে ৩০ সেকেন্ড, কিন্তু ৬০ সেকেন্ডের leeway-র ভেতরে — গৃহীত; সার্ভারের ঘড়ি হুবহু মেলে না (পর্ব ৩৯)। (৭) ৯০ সেকেন্ডে expired 90 s ago. Leeway যত বড়, চুরি যাওয়া token তত বেশি বাঁচে — তাই কয়েক সেকেন্ড থেকে এক-দুই মিনিট।
মেয়াদ ছোট, কারণ ফেরানো কঠিন। JWT একবার দিলে মেয়াদ পর্যন্ত বৈধ; তাই access token কয়েক মিনিট, সাথে store-এ রাখা refresh token (প্রতিবার ব্যবহারে বদলায়), আর জরুরি বাতিলের জন্য deny list বা token version — এই trade-off-এর হিসাব পর্ব ১৫-এর অংশ ৬-এ। ব্রাউজার-নির্ভর অ্যাপে প্রায়ই সবচেয়ে সরল আর নিরাপদ নকশা হলো ব্রাউজারের হাতে শুধু HttpOnly session cookie, আর gateway বা BFF ভেতরের সার্ভিসের জন্য স্বল্পমেয়াদি token বানায় (পর্ব ১১-এর BFF দেখুন)।
৫ · Authorization — RBAC, ABAC, ReBAC আর প্রতিটি সার্ভিসে যাচাই
ব্যাংকের কাউন্টারের কথা আবার ভাবুন। ক্যাশিয়ার পরিচয়পত্র দেখে authentication সারলেন। তারপর প্রশ্ন: এই অ্যাকাউন্ট থেকে টাকা তোলার অধিকার কি আপনার আছে? আপনি গ্রাহক — এটুকু যথেষ্ট নয়; অ্যাকাউন্টটি আপনারই হতে হবে, আর বড় অঙ্কে ম্যানেজারের সই লাগবে। Authorization-এর তিনটি প্রধান মডেল এই তিন রকম প্রশ্নের মতো:
| মডেল | প্রশ্নটি | ভালো যেখানে | দুর্বলতা |
|---|---|---|---|
| RBAC (role-based) | এই ভূমিকার কি এই কাজের অনুমতি আছে? | সরল, অডিট সহজ: customer, support, admin | “কোন বস্তু” দেখে না; ব্যতিক্রম বাড়লে role-এর বিস্ফোরণ |
| ABAC (attribute-based) | ব্যবহারকারী, বস্তু আর পরিস্থিতির বৈশিষ্ট্য কি নিয়ম মেলে? | অঞ্চল, সময়, অঙ্ক, tenant, ডিভাইস — সূক্ষ্ম নিয়ম | নিয়ম জটিল হয়; পরীক্ষা আর ব্যাখ্যা কঠিন |
| ReBAC (relationship-based) | এই মানুষ আর এই বস্তুর মাঝে কি দরকারি সম্পর্ক আছে? | শেয়ার করা ডকুমেন্ট, ফোল্ডার, দল, সংগঠন | সম্পর্কের graph-কে দ্রুত আর সামঞ্জস্যপূর্ণ রাখা কঠিন |
ReBAC-এর সবচেয়ে উদ্ধৃত উদাহরণ Google-এর Zanzibar paper (USENIX ATC, ২০১৯) — প্রকাশিত বর্ণনা অনুযায়ী একটি কেন্দ্রীয় ব্যবস্থা, যা “user U, document D-এর viewer” ধরনের সম্পর্ক জমা রাখে আর বহু পণ্যের অনুমতি-প্রশ্নের উত্তর দেয়। SpiceDB আর OpenFGA-র মতো open-source প্রকল্প নিজেদের Zanzibar-অনুপ্রাণিত বলে পরিচয় দেয়। নিয়মকে কোড থেকে আলাদা করে “policy as code” লেখার জন্য Open Policy Agent-এর মতো policy engine-ও প্রচলিত। মডেল যেটিই হোক, বাস্তবে প্রায় সবসময় মিশ্রণ: role দিয়ে মোটা ভাগ, তারপর attribute আর ownership দিয়ে সূক্ষ্ম যাচাই।
সবচেয়ে বড় ফাঁক — বস্তু-স্তরের যাচাই না থাকা
রিনা নিজের অর্ডার দেখছেন: GET /v1/orders/o-101. URL-এর শেষটা বদলে o-202 লিখলে কী হয়? সার্ভিস যদি শুধু দেখে “token বৈধ, role customer, customer অর্ডার পড়তে পারে” — তাহলে কারিমের অর্ডার রিনার পর্দায়। একে বলে BOLA (broken object level authorization), পুরোনো নাম IDOR. OWASP API Security Top 10-এর ২০১৯ আর ২০২৩ দুই সংস্করণেই এটি এক নম্বরে। প্রতিকার একটাই নিয়ম — object-level authorization: প্রতিটি অনুরোধে, প্রতিটি বস্তুর জন্য, ডেটা ফেরত দেওয়ার আগে সার্ভিস নিজে মেলাবে “এই বস্তুটি কি এই ব্যবহারকারীর (বা তাঁর tenant-এর)?” ID এলোমেলো (UUID) করা ভালো অভ্যাস, কিন্তু অনুমান কঠিন করা অনুমতির বিকল্প নয়।
নিচে একই সাতটি অনুরোধ দুই নিয়মে: শুধু RBAC, আর RBAC-এর পরে ABAC + ownership. ডিফল্ট উত্তর “না” (deny by default):
// RBAC vs ABAC (+ ownership) on the same requests — a fictional shop; deny by default
const users = { rina: { role: "customer" }, karim: { role: "customer" },
sumi: { role: "support", region: "dhaka" }, admin1: { role: "admin" } };
const orders = { "o-101": { owner: "rina", region: "dhaka", amount: 1200 },
"o-202": { owner: "karim", region: "ctg", amount: 8000 } };
const ROLE_PERMS = { customer: ["order:read"], support: ["order:read", "order:refund"], admin: ["*"] };
function rbac(u, action) { // only asks: does the ROLE have this action?
const p = ROLE_PERMS[users[u].role] || [];
return p.includes("*") || p.includes(action) ? ["ALLOW", "role " + users[u].role] : ["DENY", "role lacks " + action];
}
function abac(u, action, id, hour) { // role AND attributes of user, object and context
const [r, why] = rbac(u, action); if (r === "DENY") return [r, why];
const usr = users[u], o = orders[id];
if (!o) return ["DENY", "no such order"];
if (usr.role === "customer" && o.owner !== u) return ["DENY", "not the owner (BOLA caught)"];
if (usr.role === "support") {
if (usr.region !== o.region) return ["DENY", "region " + o.region + " is not " + usr.region];
if (hour < 9 || hour >= 18) return ["DENY", "outside shift 09-18"];
if (action === "order:refund" && o.amount > 5000) return ["DENY", "refund > 5000 needs approval"];
}
return ["ALLOW", "all conditions met"];
}
const reqs = [["rina", "order:read", "o-101", 11], ["rina", "order:read", "o-202", 11],
["sumi", "order:refund", "o-101", 14], ["sumi", "order:refund", "o-101", 22],
["sumi", "order:refund", "o-202", 14], ["karim", "order:refund", "o-202", 10],
["admin1", "order:refund", "o-202", 3]];
console.log("user action order hour RBAC ABAC+owner reason");
for (const [u, a, id, h] of reqs) {
const r = rbac(u, a)[0], [x, why] = abac(u, a, id, h);
console.log(u.padEnd(7) + a.padEnd(14) + id.padEnd(7) + String(h).padStart(4) + " " +
r.padEnd(7) + x.padEnd(12) + why + (r !== x ? " <-- differs" : ""));
}আউটপুট:
user action order hour RBAC ABAC+owner reason
rina order:read o-101 11 ALLOW ALLOW all conditions met
rina order:read o-202 11 ALLOW DENY not the owner (BOLA caught) <-- differs
sumi order:refund o-101 14 ALLOW ALLOW all conditions met
sumi order:refund o-101 22 ALLOW DENY outside shift 09-18 <-- differs
sumi order:refund o-202 14 ALLOW DENY region ctg is not dhaka <-- differs
karim order:refund o-202 10 DENY DENY role lacks order:refund
admin1 order:refund o-202 3 ALLOW ALLOW all conditions met
দ্বিতীয় সারিটিই BOLA: customer-এর order:read আছে, তাই RBAC ALLOW, অথচ অর্ডারটি কারিমের — ownership যাচাই ধরল not the owner (BOLA caught). সুমির ক্ষেত্রে RBAC তিনবারই ALLOW, কিন্তু ABAC রাত ১০টায় (outside shift 09-18) আর চট্টগ্রামের অর্ডারে (region ctg is not dhaka) না বলল; ৮,০০০ টাকার অর্ডারটি refund > 5000 নিয়মেও আটকাত, কিন্তু প্রথম মেলা কারণটিই ছাপা হয়। কারিমের role-এ refund-ই নেই। শেষ সারিটি ভাবার মতো: admin রাত ৩টায় ৮,০০০ টাকা refund করছেন, আর দুই নিয়মই ALLOW — সব-পারা * least privilege-এর উল্টো (অনুশীলন ৩)।
অনুমতি যাচাই হবে যেখানে ডেটা আছে। Gateway মোটা দাগের যাচাই করতে পারে (token বৈধ, scope আছে), কিন্তু “এই অর্ডারটি কার” জানে শুধু Orders service. তাই প্রতিটি সার্ভিস নিজের বস্তুর জন্য নিজে যাচাই করে — Saltzer-Schroeder-এর complete mediation. নিয়ম এক জায়গায় লিখে (policy engine বা একটি ভাগ করা library) সব সার্ভিসে একইভাবে প্রয়োগ করলে ভুলও কমে। আর তালিকা-প্রশ্নে (GET /orders) query-তেই WHERE owner_id = ? — পরে ছাঁকা নয়।
৬ · OAuth 2.0 আর OpenID Connect — কে কাকে কী অনুমতি দেয়
একটি কাল্পনিক হিসাবের অ্যাপ আপনার অনলাইন বাজারের অর্ডার-তালিকা পড়তে চায়। পুরোনো সমাধান ছিল অ্যাপকে নিজের password দেওয়া — তাতে অ্যাপ সব পারে, চিরকাল। OAuth 2.0 (RFC 6749, ২০১২) এই সমস্যার উত্তর: password নয়, একটি সীমিত, মেয়াদি, বাতিলযোগ্য অনুমতি — access token. বাড়ির চাবির বদলে “শুধু গ্যারেজ, শুধু আজ” লেখা একটি পাস।
চারটি ভূমিকা: resource owner (আপনি), client (হিসাবের অ্যাপ), authorization server (login আর token দেয়), আর resource server (যেমন Orders API, যেখানে ডেটা থাকে)। অনুমতির পরিধি লেখা থাকে scope-এ, যেমন orders.read. খেয়াল করুন, OAuth নিজে বলে না “ব্যবহারকারী কে” — সেটি বলে তার ওপরের স্তর OpenID Connect (OIDC, ২০১৪), যা যোগ করে একটি ID token: client-এর জন্য লেখা একটি JWT, যাতে sub, iss, aud (= client-এর ID), exp আর nonce.
Authorization code flow + PKCE
ব্রাউজার আর মোবাইল অ্যাপের জন্য আজকের মানক পথ authorization code flow, সাথে PKCE (RFC 7636, ২০১৫)। PKCE-র মূল ভাবনা: অ্যাপ শুরুতেই একটি গোপন এলোমেলো স্ট্রিং বানায় আর authorization server-কে দেখায় শুধু তার hash. পরে code ভাঙিয়ে token নেওয়ার সময় আসল স্ট্রিংটি দেখাতে হয়। পথে কেউ code দেখে ফেললেও স্ট্রিং ছাড়া তা অকেজো। নিচের verifier আর challenge RFC 7636-এর Appendix B-র প্রকাশিত উদাহরণ থেকে, code-টি RFC 6749-এর উদাহরণ থেকে, ছোট করে লেখা:
- Client অ্যাপ
bazar-webpublic client - ব্রাউজার
front channel - Authorization server
id.examplelogin, token - Orders API
resource serveraud = orders-api
| ধাপ | |
| যা পাঠানো হলো | |
| code_verifier কে জানে | |
| যাচাই |
- অ্যাপের ভেতরে প্রস্তুতি। অ্যাপ একটি এলোমেলো
code_verifierবানাল (৪৩ থেকে ১২৮ অক্ষর; উদাহরণেdBjftJ…EjXk), আর তার SHA-256-এর Base64URL রূপ —code_challenge, এখানেE9Melh…w-cM. সাথে একটি এলোমেলোstate, যাতে পরে ফেরত আসা উত্তর নিজের শুরু করা কি না মেলানো যায়। - ব্রাউজার যায় authorization server-এ। URL-এ
response_type=code,client_id, নিবন্ধিতredirect_uri,scope=openid orders.read,state, OIDC-র এলোমেলোnonce(পরে ID token-এ ফিরে আসবে) আর শুধু challenge. Verifier অ্যাপেই রইল। Server দেখে নিল client আর redirect_uri আগে নিবন্ধিত কি না — অজানা ঠিকানায় code পাঠানো হয় না। - Login আর সম্মতি — অ্যাপ password দেখে না। ব্যবহারকারী authorization server-এর নিজের পাতায় password আর passkey বা MFA দেন, তারপর দেখেন “bazar-web আপনার অর্ডার পড়তে চায়” — মঞ্জুর। সব login এক জায়গায়, তাই MFA আর সন্দেহজনক login ধরাও এক জায়গায়।
- Code ফেরত, ব্রাউজারের পথে। Server ব্রাউজারকে redirect_uri-তে পাঠাল
code=SplxlOBeZQ…আর একইstate-সহ। Code token নয়: অল্প মেয়াদের (RFC 6749 সর্বোচ্চ ১০ মিনিট সুপারিশ করে), একবার ব্যবহারযোগ্য, আর verifier ছাড়া ভাঙানো যায় না। - Back channel-এ code ভাঙানো। অ্যাপ সরাসরি server-এর
/token-এ TLS-এ পাঠাল code, redirect_uri আর আসলcode_verifier(confidential client হলে নিজের পরিচয়ও)। Server verifier-এর SHA-256 হিসাব করে আগের challenge-এর সাথে মেলাল — মিলল। Code-টি এখন খরচ হয়ে গেছে; দ্বিতীয়বার এলে প্রত্যাখ্যান। - তিনটি token, তিন কাজে। উত্তরে এলো ছোট মেয়াদের access token (
aud= orders-api,scope= orders.read), একটি refresh token, আর OIDC-র ID token. অ্যাপ ID token যাচাই করল (স্বাক্ষর, iss, aud = নিজের client_id, exp, nonce) — জানল ব্যবহারকারী কে। তারপর Orders API ডাকল access token দিয়ে; API নিজে স্বাক্ষর, iss, aud, exp আর scope মেলাল, তারপর ownership.
| Token | কার জন্য | কী বলে | ভুল ব্যবহার |
|---|---|---|---|
| Access token | Resource server (API) | এই client এই scope-এ, এতক্ষণ | দীর্ঘ মেয়াদ; সব API-তে একই aud |
| Refresh token | শুধু authorization server | নতুন access token চাওয়ার অধিকার | API-তে পাঠানো; rotation ছাড়া রাখা |
| ID token (OIDC) | Client অ্যাপ | কে, কখন, কীভাবে login করলেন | API ডাকতে ব্যবহার — এর aud API নয়, client |
কোন flow কোথায়
- ব্রাউজারের single-page app আর মোবাইল অ্যাপ: authorization code + PKCE. এদের ভেতরে গোপন কিছু রাখা যায় না, তাই এরা “public client”. আগে চলত implicit flow — access token সরাসরি URL-এ ফেরত, ইতিহাস আর অন্য script-এর নাগালে। OAuth 2.0 Security Best Current Practice (লেখার সময়ে RFC 9700, ২০২৫) implicit flow আর password-দেওয়া grant ব্যবহার না করার পরামর্শ দেয়, আর সব client-এ PKCE-র সুপারিশ করে; খসড়া OAuth 2.1-এও এগুলো বাদ।
- সার্ভারে চলা ওয়েব অ্যাপ (confidential client): একই code flow, সাথে client-এর নিজের secret বা আরও ভালো, private key-এ সই করা পরিচয়; তবু PKCE.
- সার্ভিস থেকে সার্ভিসে, কোনো মানুষ নেই: client credentials grant — রাতের batch job নিজের পরিচয়ে token নেয়, নির্দিষ্ট scope আর
aud-সহ। একই data center-এর ভেতরে অনেক সময় service mesh-এর mTLS পরিচয়ই যথেষ্ট (পর্ব ৪৭), আর ব্যবহারকারীর হয়ে ভেতরে কাজ চালাতে হলে token exchange-এর মতো পদ্ধতি।
৭ · Encryption — পথে, সংরক্ষণে আর envelope encryption
পথে (in transit): বাইরের প্রতিটি সংযোগ TLS-এ, আজ TLS 1.3 (RFC 8446, ২০১৮) পছন্দ — handshake, certificate আর বিশ্বাসের শিকল CN কোর্সের TLS 1.3 handshake আর বাস্তবে HTTPS অংশে। Edge-এ TLS শেষ হলে edge থেকে origin পর্যন্ত নতুন TLS, আর ভেতরে সার্ভিসে সার্ভিসে mTLS — দুই পক্ষই certificate দেখায়, ফলে সংযোগ এনক্রিপ্ট আর “কে ডাকছে” প্রমাণিত। হাজারো সার্ভিসের certificate হাতে বদলানো অসম্ভব, তাই service mesh বা SPIFFE-ধাঁচের workload পরিচয়-ব্যবস্থা ঘণ্টা বা দিনের মেয়াদি certificate নিজে থেকে ঘোরায়।
সংরক্ষণে (at rest): ডিস্ক, backup আর object store-এর ডেটা এনক্রিপ্ট। এটি ঠেকায় হারানো ডিস্ক, ফেলে দেওয়া backup বা ভুলে প্রকাশ্য snapshot — কিন্তু চলমান অ্যাপের BOLA নয়, কারণ অ্যাপ খোলা ডেটাই দেখে। তাই এটি অনুমতি-যাচাইয়ের বিকল্প নয়, আরেকটি স্তর।
Envelope encryption — খামের ভেতরে খাম
সব ডেটা একটি চাবিতে এনক্রিপ্ট করলে দুই সমস্যা: চাবি ফাঁস হলে সব খোলা, আর চাবি বদলাতে হলে টেরাবাইট ডেটা নতুন করে এনক্রিপ্ট। Envelope encryption এর সমাধান — দুই স্তরের চাবি। প্রতিটি ফাইল বা সারির জন্য একটি নতুন DEK (data encryption key), যা দিয়ে ডেটা স্থানীয়ভাবে AES-GCM-এর মতো authenticated encryption-এ বন্ধ হয়। তারপর DEK-কেই মোড়ানো (wrap) হয় একটি KEK (key encryption key) দিয়ে, যা কখনো KMS ছেড়ে বেরোয় না — প্রায়ই ভেতরে একটি HSM. ব্যাংকের লকারের মতো: আপনার বাক্সের চাবি আপনার কাছে, কিন্তু ভল্টের দরজা খোলে শুধু ম্যানেজারের চাবিতে, আর প্রতিবার খাতায় সই।
- Reports service
tenant t-17 - KMS
kek-reports v2KEK বের হয় না - Object store
r-5521.binশুধু ciphertext
| খোলা DEK কোথায় | |
| Store-এ যা আছে | |
| KMS-এ যা গেল | |
| Audit log |
- লেখা ১ — নতুন DEK. Reports service একটি ল্যাব রিপোর্ট রাখবে। সে KMS-কে বলল “kek-reports দিয়ে একটি data key চাই”। KMS আগে দেখল এই সার্ভিসের সেই চাবি ব্যবহারের অনুমতি আছে কি না, তারপর দিল দুটি জিনিস: খোলা ২৫৬-বিট DEK আর KEK দিয়ে মোড়া একই DEK. ঘটনাটি audit log-এ উঠল।
- লেখা ২ — স্থানীয় encrypt. Service নিজের ভেতরেই DEK দিয়ে ফাইল এনক্রিপ্ট করল (AES-GCM, নতুন nonce), সাথে “additional data” হিসেবে tenant আর রিপোর্টের ID — অন্য জায়গায় কপি করলে খোলার সময় ধরা পড়ে। ফাইলটি KMS-এ যায়নি।
- লেখা ৩ — খামসহ রাখা, চাবি ভোলা। Store-এ একসাথে রাখা হলো ciphertext, মোড়া DEK, KEK-এর সংস্করণ (v2) আর nonce; খোলা DEK memory থেকে মোছা হলো। এখন store-এর admin বা চুরি যাওয়া backup — কেউই রিপোর্ট পড়তে পারবেন না।
- পড়া ১ — খাম আনা। চিকিৎসক রিপোর্টটি খুললেন (তাঁর অনুমতি অংশ ৫-এর মতো আগেই যাচাই)। Service store থেকে ciphertext আর মোড়া DEK আনল।
- পড়া ২ — KMS-এ শুধু ছোট চাবি. Service মোড়া DEK (প্রায় ৬৪ byte, ধরা যাক) KMS-এ পাঠাল। KMS নীতি মেলাল — কোন সার্ভিস, কোন tenant — তারপর খোলা DEK দিল আর audit log-এ লিখল। চুরি করা ডেটা খোলার চেষ্টা এখানেই আটকায়, আর log-এ থাকে।
- পড়া ৩ — স্থানীয় decrypt. GCM-এর tag মিলল — কেউ একটি বিটও বদলায়নি। KMS-এর খরচ আর latency কমাতে খোলা DEK কয়েক মিনিট memory-তে cache থাকতে পারে — একটি সচেতন trade-off. KMS বিকল হলে পড়া আটকায়, তাই timeout আর retry (পর্ব ৪৮)।
এবার key rotation-এর হিসাব। ৫,০০০টি ফাইল, আকার ১ KB থেকে ১৬ MB-এর মধ্যে এলোমেলো (seed নির্দিষ্ট); KEK v1 থেকে v2-তে বদলাই। “Encrypt” এখানে খেলনা XOR — আসল cryptography নয়, শুধু কোন চাবি কাকে মোড়ায় তার হিসাব:
// Envelope encryption key hierarchy — a SIMULATION. "wrap"/"encrypt" are toy XOR, NOT real crypto.
function mulberry32(a) { return () => { a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
const rand = mulberry32(52), N = 5000, WRAPPED = 64; // ধরা যাক: wrapped DEK ~64 bytes
const toy = (key, x) => (x ^ key) >>> 0; // XOR twice = original (toy only!)
const kms = { v1: 0x5eed0001 }; let active = "v1", kmsCalls = 0; // KEK never leaves the "KMS"
const kmsWrap = dek => { kmsCalls++; return { kek: active, w: toy(kms[active], dek) }; };
const kmsUnwrap = e => { kmsCalls++; return toy(kms[e.kek], e.w); };
const objects = [];
for (let i = 0; i < N; i++) { // write path: fresh DEK per object
const dek = Math.floor(rand() * 2 ** 32) >>> 0, size = Math.round(2 ** (10 + rand() * 14)); // 1 KB..16 MB
objects.push({ size, check: toy(dek, i), env: kmsWrap(dek) }); // plaintext DEK is then forgotten
}
const dataBytes = objects.reduce((s, o) => s + o.size, 0);
const gb = b => (b / 2 ** 30).toFixed(2) + " GiB", mb = b => (b / 2 ** 20).toFixed(2) + " MiB";
console.log(N + " objects, data " + gb(dataBytes) + ", KMS calls for writes: " + kmsCalls);
kms.v2 = 0x5eed0002; active = "v2"; kmsCalls = 0; // rotate the KEK
for (const o of objects) o.env = kmsWrap(kmsUnwrap(o.env)); // re-wrap every DEK, data untouched
console.log("Rotate KEK v1 -> v2: re-wrapped " + N + " DEKs = " + mb(N * WRAPPED) + ", KMS calls " + kmsCalls);
console.log(" versus re-encrypting all data: " + gb(dataBytes) + " (" +
Math.round(dataBytes / (N * WRAPPED)) + "x more bytes)");
const ok = objects.every((o, i) => toy(kmsUnwrap(o.env), o.check) === i);
console.log("All objects still decrypt after rotation: " + ok + "; old KEK v1 still needed: " +
objects.some(o => o.env.kek === "v1"));
// blast radius: one DEK leaks (say, from a crash dump) -> only the object it encrypted is exposed
const leak = objects[7].size; // object #7's DEK leaks
console.log("One leaked DEK exposes 1 object, " + (leak / 1024).toFixed(0) + " KiB = " +
(100 * leak / dataBytes).toFixed(3) + "% of the data; the other " + (N - 1) + " stay sealed");আউটপুট:
5000 objects, data 8.18 GiB, KMS calls for writes: 5000
Rotate KEK v1 -> v2: re-wrapped 5000 DEKs = 0.31 MiB, KMS calls 10000
versus re-encrypting all data: 8.18 GiB (27454x more bytes)
All objects still decrypt after rotation: true; old KEK v1 still needed: false
One leaked DEK exposes 1 object, 1765 KiB = 0.021% of the data; the other 4999 stay sealed
লেখার সময় প্রতিটি ফাইলে একটি করে KMS call — মোট 5000. KEK বদলাতে প্রতিটি DEK খুলে নতুন KEK-এ আবার মোড়ানো (unwrap + wrap, তাই 10000 call) — ছোঁয়া হলো মাত্র 0.31 MiB চাবি, অথচ ডেটা 8.18 GiB; সব ডেটা নতুন করে এনক্রিপ্ট করলে প্রায় 27454 গুণ বেশি বাইট। আবার মোড়ানোর পর সব ফাইল খোলে (true), আর কোনো খাম আর v1-এর ওপর নির্ভর করে না (false) — তাই পুরোনো KEK এখন নিরাপদে অবসরে পাঠানো যায়। অনেক KMS পুরোনো চাবির সংস্করণ রেখে দেয়, তখন আবার মোড়ানো ঐচ্ছিক (পণ্যভেদে আলাদা)। শেষ লাইন blast radius: একটি DEK ফাঁস হলে খোলা যায় শুধু একটি ফাইল, এখানে মোট ডেটার 0.021%.
চাবি হারানো মানে ডেটা হারানো। KMS-এ KEK মুছে ফেললে, বা tenant-এর চাবি ভুলে বাতিল করলে, সেই চাবিতে মোড়া সব DEK — আর তাই সব ডেটা — চিরতরে অপাঠ্য। তাই চাবি মোছায় দেরি-সহ অনুমোদন, চাবির নিজের backup নীতি, আর disaster recovery-র পরিকল্পনায় KMS-কে ধরা (পর্ব ৫১)। উল্টো দিকে এটিই crypto-shredding: একজন tenant চলে গেলে তাঁর চাবি নষ্ট করলেই সব backup-সহ তাঁর ডেটা অপাঠ্য — প্রতিটি টেপ খুঁজে মুছতে হয় না।
৮ · Secrets, ইনপুট, tenant আলাদা রাখা, audit আর privacy
Secrets — কোডে নয়, image-এ নয়, log-এ নয়
Database-এর password, API key, signing key — এগুলো secret. সবচেয়ে পরিচিত ভুল এদের কোডে, Git-এ, Docker image-এ বা খোলা config-এ রাখা; Git-এর ইতিহাস থেকে মোছাও কঠিন। নিয়মগুলো:
- এক জায়গায়, পাহারায়: একটি secrets manager (Vault-ধাঁচের বা cloud-এর), যেখানে কে কোনটি পড়বে তা নীতিতে লেখা আর প্রতিটি পড়া log হয়; অ্যাপ নিজের workload পরিচয় দেখিয়ে secret আনে।
- স্বল্পায়ু: short-lived credential — কিছু ব্যবস্থা প্রতিটি অ্যাপ-কপির জন্য আলাদা, এক ঘণ্টার database user বানিয়ে পরে নিজে মুছে দেয়; ফাঁস হলেও জানালা ছোট।
- ঘোরানো যায় এমন নকশা: পুরোনো আর নতুন দুটি secret একসাথে বৈধ থাকবে, যাতে rotation-এ downtime না হয় — JWT-এর
kid-এর মতোই। - Environment variable-ও সাবধানে: crash report বা
env-এর dump-এ এগুলো বেরিয়ে আসে। Log-এAuthorizationheader, cookie আর password মাস্ক করা নিয়ম (পর্ব ৫০); CI pipeline-এর secret আর build-এর শিকল (supply chain) পর্ব ৫৩-এ।
ইনপুট যাচাই আর আউটপুট encoding — দুটি নকশার নিয়ম
Input validation মানে trust boundary-তেই schema মেলানো: ধরন, দৈর্ঘ্য, পরিসর, allow-list — “পরিমাণ ১ থেকে ১০০-এর পূর্ণসংখ্যা”। কিন্তু একা যথেষ্ট নয়, কারণ বৈধ নামেও (O'Brien) বিশেষ চিহ্ন থাকে। দ্বিতীয় নিয়ম: ডেটা কখনো কোডের সাথে মিশবে না। SQL-এ parameterized query — ডেটা আলাদা পথে যায়, তাই query-র অর্থ বদলাতে পারে না (DBMS কোর্সের parameterised query-র অংশ দেখুন)। HTML-এ দেখানোর সময় প্রসঙ্গ অনুযায়ী output encoding — HTML-এর লেখা, attribute, URL বা JavaScript-এর জন্য আলাদা নিয়ম, সাথে Content Security Policy; এতে XSS-এর ঝুঁকি কমে। আর সার্ভার যদি ব্যবহারকারীর দেওয়া URL থেকে কিছু আনে (ছবি, webhook), তাহলে গন্তব্যের allow-list আর ভেতরের ঠিকানায় যাওয়া নিষেধ — নইলে সার্ভার নিজেই ভেতরের দরজা খুলে দিতে পারে।
Multi-tenant isolation — এক ভবনে অনেক দোকান
একটি SaaS-এ শত শত প্রতিষ্ঠান (tenant) একই সফটওয়্যার আর প্রায়ই একই database ভাগ করে; এক দোকানের হিসাব আরেক দোকানে যাওয়াই এখানে সবচেয়ে বড় বিপর্যয়। Multi-tenancy-র তিন ধাপ: (ক) pool — সবার জন্য একই টেবিল, প্রতিটি সারিতে tenant_id, আর প্রতিটি query-তে বাধ্যতামূলক ছাঁকনি; PostgreSQL-এর মতো database-এ row-level security দিয়ে নিয়মটি database নিজেই প্রয়োগ করে (DBMS কোর্সের RLS অংশ)। (খ) bridge — প্রতি tenant-এর আলাদা schema. (গ) silo — বড় বা নিয়ন্ত্রিত গ্রাহকের জন্য আলাদা database বা পুরো আলাদা stack. যত আলাদা, তত নিরাপদ আর তত দামি। সাথে tenant-প্রতি DEK (অংশ ৭), cache key-তে tenant (পর্ব ১৭), আর tenant-প্রতি rate limit — যাতে একজনের ভিড়ে অন্যরা না ভোগেন।
Audit log — কে, কখন, কী
সাধারণ log সমস্যা খোঁজার জন্য; audit trail জবাবদিহির জন্য। সংবেদনশীল প্রতিটি কাজে — login, ভূমিকা বদল, refund, রোগীর রেকর্ড খোলা, চাবির ব্যবহার — লিখুন কে (মানুষ বা সার্ভিস), কখন, কোন বস্তু, কী ফল, কোন অনুরোধ-ID. নিয়ম তিনটি: append-only (কাজ যিনি করেন তিনি log মুছতে পারেন না), কারচুপি ধরা যায় এমন (প্রতিটি entry-তে আগেরটির hash), আর log-এ কোনো secret নয়। মুদি দোকানের বাকির খাতার মতো: পাতা ছেঁড়া যায় না, প্রতিটি লাইনে সই।
Privacy by design — কম রাখুন, ভাগ করে রাখুন
Privacy by design-এর মূল কথা: যে তথ্য রাখেননি, তা ফাঁসও হয় না। Data minimisation — শুধু দরকারি তথ্য, দরকারি সময় পর্যন্ত (retention-এর নিয়মসহ)। Data classification — প্রতিটি field-এ একটি শ্রেণি: প্রকাশ্য, ভেতরের, গোপন, সীমিত (জাতীয় পরিচয়পত্র নম্বর, স্বাস্থ্যতথ্য, card নম্বর)। শ্রেণি থেকেই নিয়ম আসে: কোনটি এনক্রিপ্ট, কোনটি log-এ মাস্ক, কোনটি test database-এ নকল ডেটায় বদলাবে। ইউরোপের GDPR (২০১৮ থেকে কার্যকর) “data protection by design and by default”-কে আইনি দায়িত্ব বানিয়েছে। বাংলাদেশে ব্যক্তিগত উপাত্ত সুরক্ষা নিয়ে আলাদা আইনের খসড়া ও প্রস্তাবিত কাঠামো কয়েক বছর ধরে আলোচনায়, আর এর অবস্থা সময়ের সাথে বদলাচ্ছে — কোনটি কার্যকর আর কোন খাতে নিয়ন্ত্রক সংস্থার আলাদা নির্দেশনা আছে, তা সরকারি সূত্রে যাচাই করে নিন।
আরও গভীরে: Bearer token যার হাতে তারই — তাই নতুন মানগুলো token-কে client-এর চাবির সাথে বাঁধে: OAuth-এর mTLS-bound token (RFC 8705, ২০২০) আর DPoP (RFC 9449, ২০২৩), যেখানে চুরি যাওয়া token অন্য যন্ত্রে অকেজো। Zanzibar paper “new enemy” সমস্যা নিয়ে আলোচনা করে — অনুমতি সরানোর পরে লেখা ডেটা যেন পুরোনো ACL-এ দেখা না যায় — আর তার উত্তর হিসেবে consistency-র একটি token (paper-এ “zookie”) ব্যবহার করে; এটি পর্ব ৪০-এর consistency-র প্রশ্নই, শুধু অনুমতির ক্ষেত্রে। Workload পরিচয়ের জন্য CNCF-এর SPIFFE/SPIRE প্রকল্প সার্ভিসগুলোকে স্বল্পমেয়াদি, স্বয়ংক্রিয়ভাবে ঘোরানো পরিচয়পত্র দেয়।
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- Authentication আর authorization-এর পার্থক্য? — প্রথমটি পরিচয় (401), দ্বিতীয়টি অনুমতি (403); দ্বিতীয়টি প্রতিটি সার্ভিসে, প্রতিটি বস্তুতে।
- Password কীভাবে রাখবেন? — Argon2id, scrypt বা bcrypt, প্রতি ব্যবহারকারীর আলাদা salt, মেপে বাছা work factor; কখনো সাধারণ hash বা encryption নয়।
- PKCE কী সমস্যা মেটায়? — পথে চুরি যাওয়া authorization code, verifier ছাড়া token-এ ভাঙানো যায় না।
- Envelope encryption কেন? — বড় ডেটা KMS-এ যায় না, KEK কখনো বের হয় না, rotation-এ শুধু ছোট DEK আবার মোড়ানো, ফাঁসের পরিধি এক ফাইল।
- BOLA কীভাবে ঠেকাবেন? — সার্ভিসে ডেটা ফেরতের আগে owner বা tenant মেলানো, তালিকা-query-তেই ছাঁকনি, deny by default, আর এর জন্য আলাদা test.
৯ · AI-এর যুগে নিরাপত্তা নকশা যাচাই
নিরাপদ login-এর নকশা চাইলে AI প্রায়ই চমৎকার শোনানো একটি তালিকা দেয়: JWT, Redis, OAuth, microservices, WAF. শব্দগুলো ভুল নয়, কিন্তু নিরাপত্তা থাকে খুঁটিনাটিতে, আর ভুলও লুকায় সেখানেই:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| Password-এর জন্য SHA-256 বা “AES দিয়ে এনক্রিপ্ট” | দ্রুত hash সস্তায় অনুমান-যাচাই হয়; encryption চাবি থাকলে উল্টে খোলা যায় | OWASP-এর Password Storage Cheat Sheet মিলিয়ে দেখুন; অংশ ৩-এর হিসাব নিজে আবার করুন |
JWT যাচাইয়ে শুধু স্বাক্ষর; aud, iss, algorithm-এর তালিকা নেই | অন্য সার্ভিসের token বা অজানা algorithm চলে যায় | অংশ ৪-এর সাত ক্ষেত্রের মতো negative test লিখে চালান |
| SPA-তে implicit flow, বা client secret মোবাইল অ্যাপের ভেতরে | পুরোনো পরামর্শ; public client-এ গোপন কিছু থাকে না | OAuth 2.0 Security BCP আর নিজের identity provider-এর ডকুমেন্টেশন |
| শুধু gateway-তে authorization, সার্ভিসে কিছু নেই | BOLA আর gateway পাশ কাটানোর পথ খোলা | প্রতিটি endpoint-এ “অন্যের ID দিলে কী হয়” test |
| বানানো সীমা আর দাম — “KMS সেকেন্ডে অসীম call নেয়”, “এই secret manager বিনামূল্যে” | Quota, latency আর দাম পণ্য ও সংস্করণভেদে আলাদা | অফিসিয়াল ডকুমেন্টেশন; ছোট load test |
| Failure mode নেই — KMS বা identity provider বিকল হলে? | নিরাপত্তার যন্ত্রও একটি dependency; fail open হলে বিপদ, fail closed হলে বিভ্রাট | Failure-mode review; staging-এ KMS-এর latency বাড়িয়ে দেখা |
| Over-engineering — ছোট অ্যাপে ReBAC graph, তিন স্তরের mesh | জটিলতা নিজেই ঝুঁকি; ভুল config-এর সুযোগ বাড়ে | Threat model থেকে শুরু: কোন ঝুঁকি, কোন প্রতিরক্ষা, তার বেশি নয় |
AI-কে কাজে লাগান threat model-এর খসড়া, চেকলিস্ট আর negative test-এর ধারণা পেতে — কিন্তু প্রতিরক্ষার প্রশ্ন করুন, অনুমান নিজে দিন, আর উৎস চান:
AI-এর দেওয়া নিরাপত্তা নকশা হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১নিরাপত্তা কেন “শেষে যোগ করার feature” নয় — ইতিহাস থেকে এর শিক্ষা কী?
১৯৭৫ সালে Saltzer আর Schroeder-এর “The Protection of Information in Computer Systems” প্রবন্ধে যে নীতিগুলো এসেছিল — সরল নকশা, ডিফল্টে নিষেধ, প্রতিটি প্রবেশে যাচাই, least privilege, খোলা নকশা (গোপনীয়তা থাকবে চাবিতে, পদ্ধতিতে নয়) — তার প্রায় সবই নকশার স্তরের সিদ্ধান্ত। কোড লেখার পরে এগুলো বসানো কঠিন, কারণ এগুলো ঠিক করে কোন উপাদান কাকে বিশ্বাস করবে আর কোথায় যাচাই হবে।
শিল্পে এই শিক্ষা এসেছে দামে। ২০০০-এর দশকের শুরুতে Windows আর ইন্টারনেট-নির্ভর সফটওয়্যারে একের পর এক worm ছড়ানোর পর, ২০০২ সালের জানুয়ারিতে Bill Gates কর্মীদের কাছে “Trustworthy Computing” memo পাঠান — নতুন feature-এর চেয়ে নিরাপত্তাকে অগ্রাধিকার দেওয়ার ঘোষণা। সেখান থেকে Microsoft-এর Security Development Lifecycle (SDL) গড়ে ওঠে, যার একটি স্তম্ভ নকশা-পর্যায়ের threat modeling; STRIDE সেই ধারারই অংশ। পরে অন্যান্য প্রতিষ্ঠান আর OWASP-এর মতো সম্প্রদায় একই শিক্ষা আরও ছড়িয়েছে: “shift left” — ভুল যত আগে ধরা পড়ে, সারানো তত সস্তা।
System design-এ এর মানে স্পষ্ট। Tenant-এর আলাদা চাবি পরে আনতে হলে সব ডেটা নতুন করে এনক্রিপ্ট আর migrate করতে হয়। প্রতিটি সার্ভিসে authorization না রেখে শুধু gateway-তে রাখলে, পরে তা যোগ করতে প্রতিটি endpoint খুঁজে বের করতে হয়। Audit log শুরু থেকে না থাকলে প্রথম বিরোধের দিন কোনো প্রমাণই থাকে না। অন্যদিকে শুরুতে নকশায় রাখা নিয়ম — deny by default, পরিচয় প্রতিটি hop-এ, চাবি আলাদা ঘরে — পরে কম খরচে শক্ত করা যায়।
এর মানে এই নয় যে প্রথম দিনেই সব প্রতিরক্ষা লাগবে। ছোট অ্যাপে threat model ছোট; ঝুঁকি বাড়লে নকশা বাড়বে। কিন্তু “কোথায় যাচাই, কোথায় চাবি, কী log” — এই তিন প্রশ্নের উত্তর নকশার প্রথম খসড়াতেই থাকা উচিত।
মূল উপলব্ধি: নিরাপত্তা architecture-এর একটি non-functional requirement, latency বা availability-র মতোই — পরে লাগাতে গেলে কাঠামোই বদলাতে হয়।
প্র ০২Password রাখার কৌশল কীভাবে খোলা লেখা থেকে Argon2-এ এলো — আর passkey কি password-কে বিদায় দেবে?
শুরুর দিকের অনেক ব্যবস্থায় password খোলা অবস্থায় ফাইলে থাকত। ১৯৭৯ সালে Robert Morris আর Ken Thompson-এর “Password Security: A Case History” প্রবন্ধে Unix-এর নকশা বর্ণিত হয়: password একমুখী ফাংশনে রূপান্তর, আর প্রতিটির সাথে একটি ছোট এলোমেলো salt — যাতে একই password-এর রূপ সবার এক না হয় আর আগে থেকে হিসাব করা তালিকা কাজে না লাগে। ধারণা দুটি — একমুখী আর salt — আজও ভিত্তি।
সমস্যা হলো হার্ডওয়্যার দ্রুত হয়েছে। ১৯৯৯ সালে Niels Provos আর David Mazières bcrypt প্রকাশ করেন, যার মূল চিন্তা “adaptive cost”: কম্পিউটার যত দ্রুত হবে, cost-এর সংখ্যা তত বাড়ানো যাবে। ২০০৯ সালে Colin Percival-এর scrypt যোগ করে memory-র খরচ, কারণ GPU আর বিশেষ চিপে হিসাব সস্তা হলেও প্রচুর memory সমান্তরালে দেওয়া দামি। ২০১৩–২০১৫ সালের উন্মুক্ত Password Hashing Competition-এ বিজয়ী হয় Argon2, যা পরে RFC 9106 (২০২১) হিসেবে লেখা হয়; তার Argon2id রূপ আজ OWASP-এর প্রথম পছন্দ। অন্যদিকে বড় ফাঁসের ঘটনায় বারবার দেখা গেছে salt-হীন দ্রুত hash — যেমন ২০১২ সালের LinkedIn ফাঁসে salt-হীন SHA-1 ব্যবহারের কথা ব্যাপকভাবে প্রকাশিত হয় — আর তখন অনেক password খুব দ্রুত উদ্ধার হয়েছিল বলে জানা যায়।
তবু hash যত শক্তই হোক, মানুষের বাছা password দুর্বল আর বারবার ব্যবহৃত, আর প্রতারণামূলক পাতায় লিখে ফেলা password-কে কোনো hash বাঁচায় না। এখানেই passkey-র যুক্তি: সার্ভারে শুধু public key, আর স্বাক্ষর domain-এ বাঁধা। ২০২২ সালের দিকে বড় platform-গুলো FIDO-ভিত্তিক passkey-র সমর্থন ঘোষণা করার পর এর ব্যবহার বেড়েছে বলে জানা যায়। কিন্তু বিদায় এখনই নয়: পুরোনো ডিভাইস, ভাগ করা কম্পিউটার, account ফেরানোর পথ আর ব্যবহারকারীর অভ্যাস — বহু বছর password আর passkey পাশাপাশি চলবে বলেই মনে হয়।
মূল উপলব্ধি: ভালো hash ক্ষতি কমায়, ঠেকায় না; আসল উন্নতি হলো এমন প্রমাণ যা সার্ভারে চুরি করার মতো কিছু রাখে না আর নকল পাতায় কাজ করে না।
প্র ০৩OAuth 2.0 এত বিতর্কিত কেন ছিল, আর implicit flow থেকে PKCE-তে সরার পেছনে কী শিক্ষা?
২০০৭ সালের দিকে কয়েকটি ওয়েব প্রতিষ্ঠানের প্রকৌশলীরা মিলে OAuth 1.0 বানান, যাতে একটি সাইট অন্য সাইটের ডেটা পড়তে ব্যবহারকারীর password না চায়। তাতে প্রতিটি অনুরোধে জটিল স্বাক্ষর লাগত, যা ঠিকভাবে বানানো কঠিন ছিল। OAuth 2.0 (RFC 6749, ২০১২) স্বাক্ষরের বদলে TLS-এর ওপর ভর করে সরল bearer token আনে, আর মোবাইল, ব্রাউজার, সার্ভার — নানা ক্ষেত্রে নানা flow. সরলতার দাম ছিল নমনীয়তা: মানটি একটি কাঠামো, একটি নির্দিষ্ট protocol কম। এ নিয়ে মতভেদে মানটির একজন প্রধান সম্পাদক Eran Hammer ২০১২ সালে প্রক্রিয়া থেকে সরে দাঁড়ান আর প্রকাশ্যে সমালোচনা করেন।
সেই সময়ের ব্রাউজারে অন্য domain-এ সরাসরি অনুরোধ পাঠানোর সুবিধা সীমিত ছিল, তাই single-page app-এর জন্য implicit flow রাখা হয়েছিল: token সরাসরি URL-এর fragment-এ ফেরত। বাস্তবে সমস্যা দেখা দিল — URL ইতিহাসে থাকে, অন্য script বা extension-এর নাগালে আসে, আর token কোন client-এর জন্য তা প্রমাণের উপায় নেই। মোবাইলে আরেক সমস্যা: একই custom URL scheme অন্য অ্যাপও দাবি করতে পারত, ফলে code ভুল অ্যাপে যেতে পারত। PKCE (RFC 7636, ২০১৫) প্রথমে এই মোবাইল সমস্যার জন্য আসে — code-কে শুরু করা client-এর একটি গোপনের সাথে বেঁধে দেয়। পরে বোঝা গেল এটি সব client-এর জন্যই উপকারী।
২০১৪ সালে OpenID Connect OAuth-এর ওপর পরিচয়ের স্তর যোগ করে — কারণ অনেকে access token-কেই “login-এর প্রমাণ” ভেবে ভুল করছিলেন। আর বছরের পর বছরের অভিজ্ঞতা জড়ো হয় OAuth 2.0 Security Best Current Practice-এ (লেখার সময়ে RFC 9700, ২০২৫): implicit আর password grant নয়, PKCE সবখানে, redirect URI হুবহু মেলানো, refresh token-এর rotation বা client-এ বাঁধা। খসড়া OAuth 2.1 এগুলোকে মূল মানে গুছিয়ে আনছে।
মূল উপলব্ধি: নিরাপত্তার মান ইতিহাসের ভুল থেকে শেখে; আজ নতুন নকশায় পুরোনো tutorial নয়, সর্বশেষ BCP আর নিজের identity provider-এর ডকুমেন্টেশনই মানদণ্ড।
প্র ০৪“Zero trust” কি একটি পণ্য? দুর্গ-মডেল থেকে এই ধারণায় আসার গল্পটা কী?
কয়েক দশক ধরে প্রতিষ্ঠানের নেটওয়ার্ক ছিল দুর্গ: বাইরে firewall, দূর থেকে ঢুকতে VPN, আর ভেতরে মোটামুটি সবাই সবার সাথে কথা বলতে পারে। দুর্বলতা হলো ভেতরের নরম অংশ — একটি ল্যাপটপ বা একটি সার্ভিস হাতছাড়া হলেই পাশের সবকিছুতে যাওয়ার পথ খোলা, যাকে বলে lateral movement বা পাশাপাশি ছড়ানো।
২০১০ সালে Forrester Research-এর বিশ্লেষক John Kindervag “zero trust” কথাটি জনপ্রিয় করেন: নেটওয়ার্কের ভেতর-বাহির দিয়ে বিশ্বাস নয়। প্রায় একই সময়ে, ২০০৯–২০১০ সালের একটি বড় সাইবার অভিযানের (প্রকাশ্যে “Operation Aurora” নামে পরিচিত) পর Google অভ্যন্তরীণভাবে BeyondCorp নামে একটি পথ নেয়, যা তারা ২০১৪ সাল থেকে কয়েকটি প্রবন্ধে প্রকাশ করে: কর্মীর অ্যাপে প্রবেশ নির্ভর করবে ব্যবহারকারী আর ডিভাইসের পরিচয় ও অবস্থার ওপর, অফিসের নেটওয়ার্কে বসে থাকার ওপর নয়। ২০২০ সালে NIST SP 800-207 এই ধারণাগুলোকে একটি সাধারণ architecture হিসেবে লেখে — policy decision point, policy enforcement point, প্রতিটি অনুরোধে যাচাই — আর পরে বিভিন্ন সরকারি নির্দেশনাও এই দিকে যায়।
সমস্যা হলো বাজারে প্রায় সবকিছুই এখন “zero trust” নামে বিক্রি হয়। আসলে এটি একটি নকশা-দিক, যা ধীরে ধীরে আসে: প্রতিটি সার্ভিসের নিজের পরিচয় (mTLS, workload identity), প্রতিটি অনুরোধে authorization, ডিভাইসের অবস্থা দেখা, সংবেদনশীল কাজে আবার যাচাই, আর সব প্রবেশের log. Firewall আর segmentation বাদ যায় না — defence in depth-এর একটি স্তর হিসেবে থাকে, কিন্তু একমাত্র ভরসা নয়। System design-এর ভাষায় এর দাম আছে: প্রতিটি hop-এ যাচাই মানে বাড়তি latency, certificate ঘোরানোর ব্যবস্থা আর একটি policy সার্ভিস, যার নিজের availability-ও নকশা করতে হয়।
মূল উপলব্ধি: zero trust মানে “কাউকে বিশ্বাস নয়” নয়, মানে “বিশ্বাস আসবে প্রমাণ থেকে, অবস্থান থেকে নয়” — আর সেই প্রমাণ প্রতিটি অনুরোধে নতুন করে।
প্র ০৫RBAC থেকে Zanzibar — authorization-কে আলাদা সার্ভিস বানানো কখন বুদ্ধিমানের কাজ, আর কখন বিপদ?
১৯৯২ সালে NIST-এর David Ferraiolo আর Richard Kuhn-এর প্রবন্ধ role-based access control-এর আনুষ্ঠানিক রূপ দেয়, যা পরে ২০০৪ সালের দিকে একটি ANSI মানে লেখা হয়। RBAC-এর জোর সরলতায়: অডিটর এক নজরে দেখেন কোন role কী পারে। কিন্তু “শুধু নিজের অঞ্চলের, অফিস-সময়ে, ৫,০০০ টাকার নিচে” ধরনের নিয়মে role-এর সংখ্যা বিস্ফোরিত হয়। ২০১৪ সালে NIST SP 800-162 ABAC-এর নির্দেশিকা দেয় — নিয়ম লেখা হবে বৈশিষ্ট্যের ওপর।
শেয়ার করা ডকুমেন্টের জগতে প্রশ্ন আবার বদলায়: “রিনা কি এই ফোল্ডারের সদস্য-দলের সদস্য?” — সম্পর্কের শিকল। Google-এর Zanzibar paper (২০১৯) দেখায় কীভাবে এমন সম্পর্ক একটি কেন্দ্রীয়, বিশ্বব্যাপী ব্যবস্থায় রেখে বহু পণ্যের জন্য একই নিয়মে, কম latency-তে উত্তর দেওয়া যায়, আর অনুমতি সরানো আর নতুন ডেটা লেখার ক্রম যেন উল্টে না যায় (“new enemy” সমস্যা) সেজন্য consistency-র token. এরপর SpiceDB, OpenFGA-র মতো প্রকল্প আর Open Policy Agent-এর মতো policy engine (যা ২০২১ সালের দিকে CNCF-এ graduated হয়) authorization-কে আলাদা স্তর বানানোর পথ সহজ করেছে।
লাভ: এক জায়গায় নিয়ম, এক রকম অডিট, সব সার্ভিসে একই উত্তর। ঝুঁকি: প্রতিটি অনুরোধ এখন এই সার্ভিসের ওপর নির্ভরশীল — বিকল হলে সব বন্ধ (fail closed) বা সব খোলা (fail open, যা বিপজ্জনক)। তাই latency আর availability-র নকশা (পর্ব ৫-এর serial chain), স্থানীয় cache আর তার পুরোনো হওয়ার সীমা, আর নিয়মের নিজের test ও review লাগে। ছোট দলের জন্য প্রায়ই যথেষ্ট সার্ভিসের ভেতরের একটি পরিষ্কার library আর database-এ ownership-এর ছাঁকনি; কেন্দ্রীয় ব্যবস্থা মানায় যখন বহু পণ্য একই বস্তু ভাগ করে।
মূল উপলব্ধি: authorization-এর মডেল বাছাই আসলে ডেটার আকৃতি বাছাই — role-এর তালিকা, বৈশিষ্ট্যের নিয়ম, না সম্পর্কের graph — আর কেন্দ্রীয় করলে এটিও একটি distributed system, তার সব consistency আর availability প্রশ্নসহ।
অনুশীলন
খাতা-কলম আর উপরের কোডগুলো পাশে রাখুন। আগে নিজে করুন, তারপর উত্তর মিলিয়ে নিন।
-
Login-এর capacity আর password-এর খরচ হিসাব করুন: একটি কাল্পনিক ভর্তি-ফলের পোর্টালে পিকে সেকেন্ডে ৩০০টি login. Argon2id-এর বাছা parameter-এ এক core-এ একটি যাচাইয়ে ৫০ ms লাগে (ধরা যাক)। (ক) শুধু hash-যাচাইয়ের জন্য কয়টি core পুরো ব্যস্ত থাকবে, আর core-গুলো ৬০%-এর বেশি ব্যবহার না করতে চাইলে কয়টি লাগবে? (খ) দল bcrypt-12-এ সরল, যাচাই প্রতি ২৪০ ms (ধরা যাক) — এখন কয়টি? (গ) একটি GPU-তে সেকেন্ডে ১,০০০টি Argon2id অনুমান (ধরা যাক) ধরলে, ছোট হাতের অক্ষর আর অঙ্ক মিলিয়ে ১০ অক্ষরের এলোমেলো password-এর পুরো জগৎ শেষ করতে কত সময়? আর password যদি সবচেয়ে প্রচলিত ১০ লাখ password-এর তালিকার একটি হয়? (ঘ) সংখ্যাগুলো থেকে নকশার দুটি সিদ্ধান্ত লিখুন।
(ক) প্রতি সেকেন্ডে কাজ = ৩০০ × ০.০৫ সেকেন্ড = ১৫ core-সেকেন্ড, অর্থাৎ ১৫টি core পুরো ব্যস্ত। ৬০% সীমায় ১৫ ÷ ০.৬ = ২৫টি core.
(খ) ৩০০ × ০.২৪ = ৭২ core পুরো ব্যস্ত; ৬০% সীমায় ৭২ ÷ ০.৬ = ১২০টি core — প্রায় পাঁচ গুণ। Work factor বাড়ানো বিনামূল্যে নয়।
(গ) জগৎ = ৩৬১০ ≈ ৩.৬৬ × ১০১৫. ÷ ১,০০০ = ৩.৬৬ × ১০১২ সেকেন্ড ÷ ৩,১৫,৫৭,৬০০ (এক বছরের সেকেন্ড) ≈ ১,১৫,৮৫৭ বছর। কিন্তু প্রচলিত ১০ লাখের তালিকায় থাকলে ১০৬ ÷ ১,০০০ = ১,০০০ সেকেন্ড ≈ ১৭ মিনিট। সংরক্ষণ একই, পার্থক্য শুধু কোন জগৎ থেকে password এসেছে।
(ঘ) (১) Login-এর জন্য আলাদা, autoscale হওয়া worker pool আর login endpoint-এ ব্যবহারকারী আর IP ধরে rate limit — যাতে login-এর ঢল বাকি সেবা না থামায়, আর অনলাইন অনুমানও ধীর হয় (পর্ব ৪৯)। (২) নিবন্ধন আর password বদলের সময় ফাঁস হওয়া আর প্রচলিত password-এর তালিকার সাথে মিলিয়ে প্রত্যাখ্যান, সাথে MFA বা passkey — কারণ (গ)-এর ১৭ মিনিট দেখায় ধীর hash একা যথেষ্ট নয়।
-
নকশা সমালোচনা করুন: একটি কাল্পনিক ক্লিনিক-অ্যাপের প্রস্তাবিত নকশা নিচে। প্রতিটি সিদ্ধান্তের ঝুঁকি (STRIDE-এর কোন অক্ষর) আর প্রতিরক্ষামূলক সংশোধন লিখুন।
(১) মোবাইল অ্যাপের ভেতরে client_secret; login-এ অ্যাপ নিজেই username/password নিয়ে /token-এ পাঠায় (password grant) (২) access token ২৪ ঘণ্টা, aud = "*", ৬টি সার্ভিস সবাই মানে (৩) ভেতরের সার্ভিস X-User-Id header যেখান থেকেই আসুক মেনে নেয় (৪) DB-এর master password Docker image-এর env-এ লেখা (৫) ল্যাব রিপোর্ট একটি চাবিতে এনক্রিপ্ট, চাবি একই bucket-এর config ফাইলে (৬) সব অনুরোধের পুরো body আর header log হয় (৭) support কর্মী যেকোনো রোগীর রেকর্ড খুলতে পারেন, কোনো log নেই- S, E: অ্যাপের ভেতরের secret অ্যাপ খুলেই পড়া যায়, আর password grant-এ অ্যাপ password দেখে — MFA-ও বসে না। সংশোধন: public client হিসেবে authorization code + PKCE, login identity provider-এর পাতায়।
- S, E: চুরি যাওয়া token এক দিন, যেকোনো সার্ভিসে চলে। সংশোধন: access token কয়েক মিনিট, প্রতি সার্ভিসের নিজের
aud, ছোট scope, refresh token rotation-সহ। - S, E: gateway পাশ কাটালেই যে কেউ যেকোনো ব্যবহারকারী। সংশোধন: সার্ভিসের মাঝে mTLS, header শুধু পরিচিত gateway থেকে, বা স্বাক্ষর করা token-এর নিজস্ব যাচাই।
- I, E: registry-তে যার প্রবেশ, তিনিই DB-এর মালিক; বদলাতে নতুন image. সংশোধন: চলার সময় secrets manager থেকে আনা, সীমিত অধিকারের আলাদা DB user, স্বল্পমেয়াদি credential, rotation.
- I: চাবি আর ডেটা একই জায়গায় — bucket পড়তে পারলেই সব খোলা, আর একটি চাবিতে সবার ডেটা। সংশোধন: envelope encryption, KEK শুধু KMS-এ, ক্লিনিক (tenant)-প্রতি আলাদা চাবি, KMS-এর প্রতিটি ব্যবহার log.
- I: log-এ token, cookie, রোগীর তথ্য — log-ই হয়ে যায় সবচেয়ে বড় ফাঁসের উৎস। সংশোধন: data classification ধরে field বাছাই,
Authorizationআর ব্যক্তিগত তথ্য মাস্ক, log-এর নিজের প্রবেশ-নিয়ন্ত্রণ আর retention (পর্ব ৫০)। - I, R, E: least privilege নেই, আর কে কী দেখল তার প্রমাণ নেই। সংশোধন: ABAC — শুধু নির্দিষ্ট ক্লিনিক আর খোলা টিকিটের রোগী; জরুরি প্রয়োজনে “break-glass” পথ, কারণ লিখে, পরে review; সব প্রবেশ append-only audit log-এ।
-
আউটপুট অনুমান করুন, তারপর একটি নিয়ম লিখুন: অংশ ৫-এর
rbac_vs_abac.js-এreqs-এর শেষে দুটি অনুরোধ যোগ করুন:["sumi", "order:read", "o-101", 8]আর["admin1", "order:read", "o-999", 12]. (ক) চালানোর আগে লিখুন — দুই সারিতে RBAC আর ABAC কী বলবে, কোন কারণে? (খ) এরপরabac-এ একটি নিয়ম যোগ করুন: admin-এর refund রাত ১০টা থেকে সকাল ৬টার মধ্যে হলে প্রত্যাখ্যান, কারণ “break-glass”-এর পথ লাগবে। Admin-এর রাত ৩টার সারিটি কী হবে?(ক) সুমি সকাল ৮টায়: RBAC
ALLOW(support-এরorder:readআছে); ABAC-এ অঞ্চল মেলে (ঢাকা), কিন্তু ৮ < ৯ —outside shift 09-18. Admin-এরo-999: RBACALLOW—*সব পারে, আর RBAC বস্তুটি আছে কি না দেখেই না; ABAC-এ বস্তু খোঁজে —no such order. চালালে নতুন দুই সারি:sumi order:read o-101 8 ALLOW DENY outside shift 09-18 <-- differs admin1 order:read o-999 12 ALLOW DENY no such order <-- differs(খ)
return ["ALLOW", …]-এর ঠিক আগে:if (usr.role === "admin" && action === "order:refund" && (hour < 6 || hour >= 22)) return ["DENY", "night admin refund: use break-glass"];এখন admin-এর রাত ৩টার সারি:
admin1 order:refund o-202 3 ALLOW DENY night admin refund: use break-glass <-- differsবাকি সারি অপরিবর্তিত। বাস্তবে break-glass মানে কারণ লিখে ঢোকার আলাদা পথ, যা সঙ্গে সঙ্গে সতর্কবার্তা পাঠায় আর পরে review হয়।
-
Threat model লিখুন: একটি কাল্পনিক অনলাইন বাজারের “password ভুলে গেছেন” flow: ব্যবহারকারী ইমেইল দেন, সার্ভার একটি link পাঠায়, link খুলে নতুন password. Data-flow-এর অংশগুলো আর trust boundary চিহ্নিত করুন, তারপর STRIDE-এর প্রতিটি অক্ষরে একটি ঝুঁকি আর নকশার প্রতিরক্ষা লিখুন।
DFD: ব্যবহারকারী → reset API (প্রক্রিয়া) → token store আর user database (data store) → ইমেইল সেবা (বাইরের পক্ষ) → inbox → link → reset API. Trust boundary: ইন্টারনেট থেকে API, API থেকে ইমেইল সেবা, আর আপনার নিয়ন্ত্রণের বাইরের inbox.
- S: অন্য কেউ link হাতে পেয়ে নিজেকে মালিক দাবি করেন। প্রতিরক্ষা: link-এ লম্বা এলোমেলো token (অনুমানযোগ্য ID নয়), store-এ তার hash, মেয়াদ কয়েক মিনিট, একবারই ব্যবহার; MFA থাকলে reset-এর পরও দ্বিতীয় factor.
- T: link-এর মধ্যে user ID বদলে অন্যের account. প্রতিরক্ষা: token থেকেই সার্ভারে user খোঁজা — URL-এর ID বিশ্বাস নয়।
- R: “আমি password বদলাইনি”। প্রতিরক্ষা: audit log (সময়, ডিভাইসের মোটা তথ্য), আর পুরোনো ইমেইল ও ফোনে বিজ্ঞপ্তি।
- I: উত্তর দেখে বোঝা যায় কোন ইমেইল নিবন্ধিত। প্রতিরক্ষা: সবসময় একই বার্তা (“নিবন্ধিত হলে ইমেইল যাবে”); token log-এ নয়, আর reset পাতায় তৃতীয় পক্ষের script নয়।
- D: কারও ইমেইলে হাজারো reset পাঠিয়ে বিরক্ত করা বা ইমেইল-খরচ বাড়ানো। প্রতিরক্ষা: ইমেইল আর IP ধরে rate limit (পর্ব ৪৯), কাজটি queue-তে।
- E: reset-এর পরও পুরোনো session আর token চালু থাকে। প্রতিরক্ষা: সফল reset-এ সব session আর refresh token বাতিল, token version বাড়ানো (পর্ব ১৫)।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমOAuth 2.0-এর authorization code flow + PKCE-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🎲 Client একটি code_verifier বানায় আর তার SHA-256 থেকে code_challenge
- 🌐 ব্রাউজার challenge আর state নিয়ে authorization server-এ যায়
- 🔑 ব্যবহারকারী server-এর পাতায় login করেন আর scope মঞ্জুর করেন
- ↩️ Server একবার-ব্যবহারযোগ্য code আর একই state নিয়ে redirect করে
- 🔁 Client back channel-এ code আর code_verifier দিয়ে token চায়
- 🛒 Access token দিয়ে API ডাকা হয়, API নিজে aud, exp আর scope মেলায়
একটি নতুন সিস্টেমে ব্যবহারকারীর password কীভাবে রাখা উচিত?
- সবার জন্য একটি সাধারণ salt দিয়ে SHA-256
- AES দিয়ে এনক্রিপ্ট করে, চাবি config ফাইলে
- প্রতি ব্যবহারকারীর আলাদা salt দিয়ে Argon2id, মেপে বাছা parameter-এ
- MD5 দুবার চালিয়ে
একটি API-তে ব্যবহারকারী A নিজের বৈধ token দিয়ে GET /v1/orders/9001 চাইলেন, আর উত্তরে এলো ব্যবহারকারী B-এর অর্ডার। সবচেয়ে সঠিক প্রতিকার কোনটি?
- অর্ডারের ID সংখ্যার বদলে UUID করা
- প্রতিটি অনুরোধে Orders service-এ অর্ডারের owner আর token-এর ব্যবহারকারী মেলানো
- WAF-এ একটি নতুন নিয়ম যোগ করা
- Token-এর স্বাক্ষর RS256 থেকে ES256 করা
OAuth 2.0-এর authorization code flow-তে PKCE মূলত কোন সমস্যা ঠেকায়?
- পথে চুরি বা ভুল জায়গায় যাওয়া authorization code দিয়ে অন্য কারও token নেওয়া
- Access token-এর ভেতরের তথ্য এনক্রিপ্ট না থাকা
- Refresh token-এর দরকারই না রাখা
- TLS ছাড়াই নিরাপদে token পাঠানো
একটি OIDC login-এর পর client অ্যাপের হাতে access token, refresh token আর ID token আছে। Orders API ডাকতে কোনটি পাঠানো উচিত, আর API কী মেলাবে?
- ID token — কারণ এতে ব্যবহারকারীর পরিচয় আছে
- Refresh token — কারণ এর মেয়াদ বেশি
- যেকোনোটি, যতক্ষণ authorization server-এর স্বাক্ষর ঠিক আছে
- Access token — API মেলাবে স্বাক্ষর, iss, aud, exp আর scope
একটি object store-এ ২০ লাখ ফাইল, মোট ১০ TB, envelope encryption-এ রাখা; প্রতিটি মোড়া DEK ৬৪ byte. KEK বদলে সব DEK আবার মোড়ালে মোটামুটি কত বাইট ছুঁতে হবে?
- প্রায় ১২৮ MB
- প্রায় ১০ TB
- মাত্র ৬৪ byte, কারণ KEK একটি
- প্রায় ১.২৮ TB
Zero trust নকশা সম্পর্কে কোনটি সঠিক?
- VPN-এ সংযুক্ত হলে ভেতরের সব সার্ভিসে প্রবেশ খোলা থাকে
- Zero trust-এ firewall-এর আর কোনো দরকার নেই
- প্রতিটি অনুরোধে পরিচয় আর অনুমতি যাচাই হয়, নেটওয়ার্কের কোথা থেকে এলো তা নির্বিশেষে
- একটি নির্দিষ্ট পণ্য কিনে বসালেই zero trust হয়ে যায়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- নিরাপত্তা নকশার গুণ: CIA কী রক্ষা করে, DFD আর trust boundary কোথায় ঝুঁকি, STRIDE কী ভুল হতে পারে; নীতি — defence in depth, least privilege, zero trust.
- Password: Argon2id/scrypt/bcrypt + আলাদা salt, মেপে বাছা cost, login-এ rate limit; সাথে MFA বা passkey. Token: algorithm allow-list, kid, স্বাক্ষর, iss, aud, exp — সব।
- Authorization প্রতিটি সার্ভিসে, প্রতিটি বস্তুতে: RBAC মোটা দাগ, ABAC আর ReBAC সূক্ষ্ম; ownership check ছাড়া BOLA; deny by default.
- Authorization code + PKCE ব্রাউজার আর মোবাইলে, implicit নয়; access token API-র, ID token client-এর, refresh token শুধু server-এর; মেশিনে মেশিনে client credentials বা mTLS.
- পথে TLS 1.3 আর mTLS; সংরক্ষণে envelope encryption — DEK প্রতি বস্তু, KEK KMS-এ, rotation-এ শুধু DEK আবার মোড়ানো; secret vault-এ, স্বল্পায়ু; log-এ secret নয়; tenant আলাদা, audit append-only, তথ্য কম।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · Deployment আর release — container, CI/CD, blue-green, canary আর zero-downtime migrationপরবর্তী পর্বনিরাপদে, ধাপে ধাপে নতুন সংস্করণ চালু করা।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Cryptography-র ভিত্তি — MAC, HMAC আর password রাখার নিয়মগভীরে যানAES-এর mode, hash, signature আর hybrid encryption.
- TLS আর HTTPS — handshake আর certificateপাশের ট্র্যাকTLS 1.3 বার্তা ধরে ধরে, PKI আর termination.
- ডেটাবেস নিরাপত্তা — injection প্রতিরোধ, least privilege আর RLSপাশের ট্র্যাকGRANT, row-level security, encryption আর audit হাতে-কলমে।