Cloud Networking — VPC, Subnet ও Security Group দিয়ে shared physical network-এর ভেতরে নিজের একচ্ছত্র জগৎ বানানো
Cloud Networking: VPCs, Subnets, Route Tables, and Security Groups
container-networking লেসন একটা single host-এর ভেতরে veth/bridge/NAT দিয়ে network ভার্চুয়ালাইজ করেছিল। এই লেসন ঠিক একই ভাবনাটা cloud-scale-এ নিয়ে যায় — VPC দিয়ে shared physical network-এর ভেতরে নিজের একচ্ছত্র CIDR block দাবি করা, subnet ও route table দিয়ে সেই block-কে public/private ভাগে সাজানো, আর security group/NACL দিয়ে প্রতিটা স্তরে traffic filter করা।
আগে এটা বুঝি
container-networking লেসনে আমরা একটা মেশিনের ভেতরে network ভার্চুয়ালাইজ করতে দেখেছিলাম: একটা namespace সম্পূর্ণ বিচ্ছিন্ন শুরু হয়, veth pair দিয়ে host-এর সাথে জোড়া লাগে, Linux bridge দিয়ে একাধিক container একে অপরের সাথে যুক্ত হয়, আর iptables MASQUERADE দিয়ে সবশেষে internet পর্যন্ত পৌঁছায়। পুরো ব্যাপারটা একটা মেশিনের kernel-এর ভেতরে ঘটেছিল — একটা host, একটা kernel, একগুচ্ছ software primitive।
আজকের প্রশ্ন: এই একই ধরনের সমস্যা — “একটা shared, physical জিনিসের ভেতরে প্রতিটা tenant-এর জন্য একটা বিচ্ছিন্ন, নিজস্ব জগৎ বানানো” — কীভাবে সমাধান হয় যখন সেই “shared, physical জিনিস” একটা মেশিন না, বরং একটা পুরো datacenter-এর নেটওয়ার্ক? AWS, GCP, Azure-এর মতো cloud provider-দের একটা datacenter-এ হাজার হাজার গ্রাহকের workload একই physical switch, একই physical router, একই fiber-এর মধ্য দিয়ে যাতায়াত করে। কীভাবে নিশ্চিত করা হয় যে একজন গ্রাহকের 10.0.0.5 ঠিকানার সাথে আরেকজন গ্রাহকের 10.0.0.5 ঠিকানার কোনো সংঘর্ষ হবে না, কেউ কারো traffic দেখতে পারবে না, অথচ প্রত্যেকেই মনে করবে তার নিজস্ব একটা গোটা নেটওয়ার্ক আছে?
উত্তরের নাম VPC — Virtual Private Cloud। এই লেসনের কেন্দ্রীয় দাবিটা এখনই বলে রাখা ভালো, কারণ পুরো লেসনটা এর চারপাশেই ঘুরবে:
আজকের লেসন চারটা স্তরে এই দাবিটা concrete করবে:
১. VPC আর CIDR block — কীভাবে একটা logically isolated network দাবি করা হয়, networking/ipv4-subnetting-cidr লেসনের ঠিক সেই CIDR গণিত পুনরায় ব্যবহার করে।
২. Subnet ও availability zone — VPC-র CIDR block-কে কীভাবে ছোট ছোট, নির্দিষ্ট data-center-বিল্ডিং-এ পিন-করা ভাগে সাজানো হয়, আর public/private-এর পার্থক্যটা আসলে কোথা থেকে আসে (spoiler: এটা কোনো magic flag না)।
৩. Route table — সেই আসল যন্ত্র, যেটা প্রতিটা subnet-এর traffic কোথায় যাবে তা ঠিক করে, আর তাই public/private-এর সিদ্ধান্তটাও আসলে এখানেই নেওয়া হয়।
৪. Security group বনাম NACL — দুই স্তরের firewall, যাদের মধ্যে পার্থক্য precisely দুইটা axis-এ: statefulness, আর scope।
শেষে থাকবে NAT gateway — networking/nat লেসনের সেই একই MASQUERADE-ধারণার একটা managed, cloud-scale সংস্করণ। আর লেসনের শেষে একটা প্রশ্ন যা এই পুরো লেসনটাকেই একটা নতুন সমস্যার মুখে দাঁড় করাবে: এই সবকিছু — VPC, subnet, route table, security group — মানুষ যদি প্রতিবার হাতে, একটা ওয়েব কনসোলে ক্লিক করে বানায়, সেটা production-এ কতদিন টেকসই?
মূল ধারণা
VPC — একটা CIDR block যেটা সম্পূর্ণ আপনার
একটা VPC তৈরি করার প্রথম আর সবচেয়ে গুরুত্বপূর্ণ সিদ্ধান্ত: একটা CIDR block বেছে নেওয়া। networking/ipv4-subnetting-cidr লেসনের ঠিক সেই ভাষায় — একটা ip/prefix, যেমন 10.0.0.0/16। এই মুহূর্ত থেকে এই পুরো address range-টা আপনার VPC-র ভেতরে সম্পূর্ণ আপনার — cloud provider-এর অন্য কোনো গ্রাহকের VPC-র সাথে এর কোনো সংঘর্ষ নেই, কারণ প্রতিটা VPC একটা আলাদা, বিচ্ছিন্ন routing domain।
VPC CIDR: 10.0.0.0/16
prefix /16 → প্রথম ১৬ bit network-part, বাকি ১৬ bit host-part
মোট ঠিকানা: 2^16 = 65,536 (network + broadcast বাদে ব্যবহারযোগ্য 65,534 — যদিও
বাস্তবে পুরোটা একসাথে কখনো "host" হিসেবে ব্যবহৃত হয় না,
subnet-এ ভাগ হয়ে যায়, যা পরের অংশে দেখব)AWS-এ VPC-র CIDR /16 থেকে /28-এর মধ্যে হতে হয় — অর্থাৎ সবচেয়ে বড় VPC ৬৫,৫৩৬টা ঠিকানা ধরে, সবচেয়ে ছোট মাত্র ১৬টা। 10.0.0.0/16, 172.16.0.0/16, 192.168.0.0/16 — এগুলো RFC1918 private range-এর ভেতর থেকেই সাধারণত বেছে নেওয়া হয় (ঠিক ipv4-subnetting-cidr লেসনের বিশেষ-ঠিকানার টেবিলের সেই তিনটা range), কারণ VPC-র ভেতরের ঠিকানা সরাসরি internet-এ routable হওয়ার দরকার নেই — ঠিক networking/nat লেসনের সেই একই যুক্তি, যা এই লেসনের শেষ অংশে আবার ফিরে আসবে।
Subnet — VPC-র CIDR block-কে ভাগ করা, প্রতিটা একটা নির্দিষ্ট availability zone-এ পিন করা
একটা একক, অভাজিত /16 ব্লকে সরাসরি instance বসানো হয় না। VPC-র CIDR block-কে ছোট ছোট subnet-এ ভাগ করা হয় — ঠিক ipv4-subnetting-cidr লেসনের subnet-splitting অংশের সেই একই bit-borrowing গণিত — আর প্রতিটা subnet বাঁধা থাকে একটা নির্দিষ্ট availability zone (AZ)-এর সাথে, অর্থাৎ cloud provider-এর একটা নির্দিষ্ট, physically পৃথক data-center-বিল্ডিং বা বিল্ডিং-গুচ্ছের সাথে। একটা subnet কখনো একাধিক AZ জুড়ে থাকতে পারে না — এই সীমাবদ্ধতাই একাধিক AZ জুড়ে redundancy design করার ভিত্তি।
প্রতিটা subnet-কে আরও দুই শ্রেণিতে ভাগ করা হয়:
- Public subnet — এমন subnet যার একটা route আছে internet gateway (IGW)-এর দিকে, তাই এখানে বসানো instance-দের সরাসরি একটা public IP থাকতে পারে আর internet থেকে সরাসরি পৌঁছানো যায়।
- Private subnet — এখানে internet gateway-এর দিকে কোনো সরাসরি route নেই। এখানকার instance-দের কোনো public IP নেই, বাইরে থেকে সরাসরি কেউ পৌঁছাতে পারে না — কিন্তু তাদের যদি বাইরের দিকে (শুধু outbound) যেতে হয়, সেটা একটা NAT gateway দিয়ে যায়, যা এই লেসনের শেষ অংশের বিষয়।
এই “public”/“private” শব্দ দুটো এখানে ইচ্ছা করেই আলগাভাবে বলা হলো — পরের অংশেই দেখব এগুলো আসলে কোনো subnet-এর নিজস্ব builtin property না, বরং route table-এর একটা সরাসরি ফলাফল।
একটা concrete subdivision — 10.0.0.0/16 থেকে ছয়টা /24
চলুন হাতে-কলমে করি। VPC 10.0.0.0/16-কে আমরা ছয়টা /24 subnet-এ ভাগ করছি — তিনটা availability zone, প্রতিটাতে একটা public আর একটা private subnet। /16 থেকে /24-এ যেতে হোস্ট-পার্ট থেকে ৮টা bit network-পার্টে borrow করতে হয় (24 − 16 = 8), তাই block size 2^8 = 256 ঠিকানা প্রতি subnet-এ, আর তৃতীয় octet-এর মান দিয়েই subnet-গুলো আলাদা হয়:
| Subnet | AZ | CIDR | Network | Broadcast | Usable range | Usable count |
|---|---|---|---|---|---|---|
| public-a | ap-south-1a | 10.0.0.0/24 | 10.0.0.0 | 10.0.0.255 | 10.0.0.1–10.0.0.254 | 254 |
| private-a | ap-south-1a | 10.0.1.0/24 | 10.0.1.0 | 10.0.1.255 | 10.0.1.1–10.0.1.254 | 254 |
| public-b | ap-south-1b | 10.0.2.0/24 | 10.0.2.0 | 10.0.2.255 | 10.0.2.1–10.0.2.254 | 254 |
| private-b | ap-south-1b | 10.0.3.0/24 | 10.0.3.0 | 10.0.3.255 | 10.0.3.1–10.0.3.254 | 254 |
| public-c | ap-south-1c | 10.0.4.0/24 | 10.0.4.0 | 10.0.4.255 | 10.0.4.1–10.0.4.254 | 254 |
| private-c | ap-south-1c | 10.0.5.0/24 | 10.0.5.0 | 10.0.5.255 | 10.0.5.1–10.0.5.254 | 254 |
এই হিসাবটা ipv4-subnetting-cidr লেসনের AND-operation দিয়েই বের করা যায় — যেমন 10.0.2.0/24-এর network: 10.0.2.99 AND 255.255.255.0 = 10.0.2.0। প্রতিটা subnet নিজের ৮-bit block-এর multiple-এ শুরু হচ্ছে (0, 1, 2, 3, 4, 5 — তৃতীয় octet-এ) — ঠিক সেই alignment-নিয়ম যা prerequisite লেসনের “danger” callout-এ ছিল। মোট 10.0.0.0/16-এ এরকম /24 subnet বসতে পারে ২৫৬টা (2^8); আমরা এখানে মাত্র প্রথম ছয়টা ব্যবহার করছি, বাকি ২৫০টা ভবিষ্যতের জন্য অব্যবহৃত পড়ে আছে।
Route table — আসল যন্ত্র, যেটা public/private সিদ্ধান্ত নেয়
এবার সেই দাবিটা প্রমাণ করি: subnet নিজে “public” বা “private” জানে না। প্রতিটা subnet একটা route table-এর সাথে যুক্ত থাকে — destination CIDR থেকে target-এর একটা explicit mapping, একদম router-এর routing table-এর মতোই:
public-route-table (public-a, public-b, public-c-এর সাথে যুক্ত):
destination target
10.0.0.0/16 local (VPC-এর ভেতরের সব traffic সরাসরি)
0.0.0.0/0 igw-0a1b2c3d (বাকি সবকিছু internet gateway-তে)
private-route-table (private-a, private-b, private-c-এর সাথে যুক্ত):
destination target
10.0.0.0/16 local
0.0.0.0/0 nat-0e5f6g7h (বাকি সবকিছু NAT gateway-তে, IGW-তে না)লক্ষ করুন — শুধু এই একটা লাইনের পার্থক্যই একটা subnet-কে public বা private বানায়: 0.0.0.0/0 কোথায় যাচ্ছে। 0.0.0.0/0 মানে “বাকি সব destination” (ipv4-subnetting-cidr লেসনের সেই default-route ধারণা) — এই route যদি internet gateway-এর দিকে যায়, subnet-টা public; NAT gateway-এর দিকে গেলে, subnet-টা private-with-outbound-access; আর 0.0.0.0/0-এর জন্য কোনো route-ই না থাকলে (শুধু local route), subnet-টা সম্পূর্ণ isolated — কোনো internet access-ই নেই, ভেতরে বা বাইরে কোনো দিকেই।
Security group বনাম NACL — দুই axis-এ নির্ভুল পার্থক্য
VPC-তে দুই স্তরের firewall আছে — security group আর NACL — আর তাদের মধ্যে পার্থক্যটা প্রায়ই ভুল বোঝা হয়। দুইটা axis দিয়ে একদম নির্ভুলভাবে আলাদা করা যায়:
| Security Group | NACL (Network ACL) | |
|---|---|---|
| Scope | Instance-level — প্রতিটা resource-এ আলাদাভাবে সংযুক্ত | Subnet-level — সেই subnet-এর সব resource-এ প্রযোজ্য |
| State | Stateful — inbound allow হলে matching response automatically allow | Stateless — inbound আর outbound সম্পূর্ণ আলাদা, দুটোই explicitly লিখতে হয় |
| Rule type | শুধু ALLOW (default-deny, কোনো explicit DENY দরকার নেই) | ALLOW এবং DENY দুটোই, numbered rule হিসেবে ক্রমানুসারে evaluate হয় |
| Evaluation | সব matching rule-এর union — কোনো priority/order নেই | rule number অনুসারে, সবচেয়ে ছোট নম্বর আগে, প্রথম match-ই জেতে |
Statefulness কেন এত গুরুত্বপূর্ণ, একটা concrete উদাহরণে দেখা যাক। একটা web-server-এর security group-এ শুধু একটা inbound rule আছে: “TCP পোর্ট 443-এ allow”। একটা client 443-এ একটা HTTPS request পাঠাল — inbound rule মিলল, request ভেতরে ঢুকল। এখন server reply পাঠাতে চায় (src=443, dst=client-এর ephemeral port)। Stateful security group এই reply-কে স্বয়ংক্রিয়ভাবে অনুমোদন করে — কোনো আলাদা outbound rule দরকার নেই, কারণ security group মনে রাখে যে এই client-টা একটা বৈধ inbound connection শুরু করেছিল, আর তারই response এটা।
কিন্তু stateless NACL-এ এই একই ইনবাউন্ড-rule-only সেটআপ ভেঙে যায়। NACL শুধু inbound rule (“443 allow”) দিয়ে request-টা ভেতরে ঢুকতে দেবে ঠিকই — কিন্তু reply পাঠানোর সময় সেটা একটা সম্পূর্ণ নতুন, স্বাধীন outbound packet হিসেবে বিচার হয় (src=443, dst=ephemeral-port), আর যদি আলাদা কোনো outbound rule না থাকে যা এই ephemeral-port range-কে (সাধারণত 1024–65535) allow করে, সেই reply ব্লক হয়ে যাবে — যদিও মূল request একদম ঠিকভাবে ভেতরে ঢুকেছিল। Connection তখন ভাঙা — client-এর কাছে মনে হবে server hang করে গেছে বা timeout করছে, অথচ আসল কারণ একটা মিসিং outbound rule।
NAT gateway — networking/nat-এর MASQUERADE ধারণার managed, cloud-scale সংস্করণ
Private subnet-এর instance-দের কোনো public IP নেই, তাই তারা সরাসরি internet-এ কিছু পাঠাতে পারে না — অথচ তাদেরও প্রায়ই বাইরে যাওয়ার দরকার হয় (একটা apt update, একটা external API call, একটা software-update চেক)। সমাধান একদম networking/nat লেসনের সেই একই ধারণা: private IP-কে rewrite করে একটা routable public IP দিয়ে internet-এ পাঠানো, আর reply-র জন্য একটা mapping রাখা।
পার্থক্য শুধু এইখানে — এই NAT-টা আপনি নিজে operate করেন না। এটা একটা cloud-provider-managed service: আপনি শুধু একটা public subnet-এ একটা NAT gateway “বসিয়ে দেন” (provision করেন), এটাকে একটা Elastic/public IP assign করেন, আর private subnet-এর route table-এ 0.0.0.0/0 -> nat-gateway-id রুট যোগ করেন। এরপর থেকে সেই যুক্তিটাই প্রযোজ্য যা networking/nat লেসনে দেখেছিলেন:
private instance (10.0.1.20) -> 93.184.216.34:443 পাঠাচ্ছে
১. route table lookup: 0.0.0.0/0 -> nat-gateway (public subnet-এ বসানো)
২. NAT gateway: src rewrite 10.0.1.20:<port> -> <NAT gateway-এর public IP>:<ephemeral-port>
(conceptually একই conntrack-mapping যা networking/nat লেসনে দেখেছিলেন)
৩. public subnet-এর route table: 0.0.0.0/0 -> internet gateway
৪. internet gateway দিয়ে প্যাকেট internet-এ বেরিয়ে যায়
৫. reply ফিরলে NAT gateway-এর mapping অনুযায়ী উল্টো rewrite -> private instance-এ ফেরতnetworking/nat লেসনের সবচেয়ে গুরুত্বপূর্ণ শিক্ষাটা এখানেও হুবহু প্রযোজ্য: এটা outbound-only। NAT gateway private subnet-এর instance-দের বাইরে যেতে দেয়, কিন্তু বাইরে থেকে কেউ প্রথম packet পাঠিয়ে সরাসরি একটা private instance-এ পৌঁছাতে পারে না — ঠিক NAT লেসনের সেই “inbound-initiation সংকট”, কারণ mapping জন্মায় শুধু outbound traffic-এ। managed হওয়ার সুবিধা: high-availability (প্রতিটা AZ-এ একটা করে NAT gateway বসানো যায়, redundancy-র জন্য), automatic scaling (bandwidth-এর জন্য কিছু configure করতে হয় না), আর কোনো conntrack টেবিল নিজে maintain করার দরকার নেই — বিনিময়ে per-GB-processed বিলিং।
ভেতরে কী ঘটছে
Software-defined মানে ভেতরে কী — overlay আর প্রতিটা hop-এ enforcement
“Software-defined, logically isolated” দাবিটা আক্ষরিক অর্থে সত্য হওয়ার জন্য cloud provider-দের ভেতরে একটা সমস্যা সমাধান করতে হয়: একই physical switch-এর মধ্য দিয়ে হাজার হাজার ভিন্ন VPC-র traffic যাচ্ছে, কিন্তু তারা কেউ কারো MAC address, কারো broadcast domain, কারো traffic দেখতে পাবে না। এটা বাস্তবায়িত হয় network overlay/encapsulation কৌশলে (যেমন VXLAN বা সমতুল্য proprietary প্রযুক্তি) — container-networking লেসনের overlay-network উল্লেখের সাথে সরাসরি সম্পর্কিত ধারণা: প্রতিটা tenant-এর packet একটা অতিরিক্ত header-এ মোড়ানো থাকে যাতে একটা unique tenant-identifier (VPC-id) থাকে, physical network শুধু সেই বাইরের header দেখে route করে, ভেতরের আসল packet-টা শুধু সঠিক tenant-এর hypervisor/host-এ পৌঁছেই unwrap হয়।
এর একটা গুরুত্বপূর্ণ ফলাফল: security group evaluation ঘটে instance-এর নিজের ভেতরে না, বরং host-এর hypervisor স্তরে — ঠিক container-networking লেসনের সেই প্যাটার্নের মতো, যেখানে iptables MASQUERADE rule container-এর namespace-এ না বসে host namespace-এ বসেছিল। VM/instance নিজে কখনো security-group-rule দেখতে বা বদলাতে পারে না, কারণ সেটা তার নিজের বাইরে, physical host-এর নিয়ন্ত্রণাধীন infrastructure-এ প্রয়োগ হয়। guest OS compromise হলেও security group bypass করা যায় না — এটাই এই architecture-এর একটা ইচ্ছাকৃত নিরাপত্তা-সীমানা।
Security group-এর statefulness আসলে একটা connection-tracking টেবিল
Security group কীভাবে “মনে রাখে” যে একটা reply বৈধ, তার আসল প্রক্রিয়াটা conceptually networking/nat লেসনের conntrack-এর সাথে হুবহু এক পরিবারের — শুধু কাজটা address rewrite না, allow/deny সিদ্ধান্ত:
inbound packet ঢুকল: src=203.0.113.5:54321 dst=10.0.0.5:443
inbound rule-এ ম্যাচ পাওয়া গেল (tcp/443) -> ALLOW
connection-tracking entry তৈরি: (tcp, 203.0.113.5:54321 <-> 10.0.0.5:443)
পরে, একই flow-এর reverse-direction packet: src=10.0.0.5:443 dst=203.0.113.5:54321
কোনো outbound rule-lookup লাগে না — connection-tracking টেবিলে
এই flow-এর reverse entry পাওয়া গেল -> ALLOW, automaticএই টেবিলটাই security group-এর “state” — আর ঠিক এই কারণেই security group-এ কোনো explicit outbound rule না থাকলেও একটা বৈধ inbound-এর reply ঠিকমতো বেরিয়ে যায়।
NACL-এর rule evaluation — সংখ্যা-ক্রমে, প্রথম match জেতে
NACL rule-গুলো numbered (যেমন 100, 110, 120…) আর evaluation হয় সবচেয়ে ছোট নম্বর থেকে শুরু করে — প্রথম যে rule-টা মেলে, সেটাই চূড়ান্ত সিদ্ধান্ত (ALLOW বা DENY), তারপর evaluation থেমে যায়। কোনো rule না মিললে শেষে একটা implicit * DENY (সব traffic) থাকে। এটা security group-এর মডেল থেকে আলাদা — security group-এ কোনো rule-order নেই, সব ALLOW-rule একসাথে evaluate হয় (কোনো একটা মিললেই allow), আর কোনো explicit DENY rule নেই।
NACL rule evaluation (সংখ্যা-ক্রমে):
#100 ALLOW tcp 443 from 0.0.0.0/0
#200 DENY tcp * from 198.51.100.0/24 <- এই rule কখনো hit হবে না
যদি #100 আগেই allow করে দেয়,
কিন্তু অন্য কোনো port-এ এই
range-কে block করার জন্য useful
#* DENY all from 0.0.0.0/0 <- implicit, সবার শেষেRule-number-এর ফাঁকা জায়গা (100, 200 — মাঝে 110, 120…) ইচ্ছাকৃত রাখা হয়, ঠিক পুরনো BASIC প্রোগ্রামের line-number-এর মতো — পরে মাঝখানে নতুন rule ঢোকানোর জন্য জায়গা।
উদাহরণ
একটা private-subnet instance-এর outbound HTTPS request — সম্পূর্ণ hop-by-hop trace
- instance 10.0.1.20 — packet তৈরিdst=93.184.216.34:443, নিজের কোনো public IP নেই
- security group (outbound) — instance-level, statefulডিফল্ট outbound rule "allow all" থাকলে pass; নতুন flow-এর connection-tracking entry তৈরি হয়
- private subnet-এর NACL (outbound) — subnet-level, statelessexplicit outbound rule (dst-port 443, বা wide-open) মিলতে হবে, নাহলে DENY
- route table lookup (private-route-table)0.0.0.0/0 -> nat-gateway (এই একটা লাইনই এই subnet-কে "private-with-outbound-access" বানিয়েছে)
- NAT gateway (public subnet-এ বসানো)src rewrite: 10.0.1.20:<port> -> <NAT-GW public IP>:<ephemeral>, mapping সংরক্ষিত
- public subnet-এর NACL (outbound)আবার stateless rule-check, subnet-boundary-তে
- public subnet-এর route table0.0.0.0/0 -> internet gateway
- internet gateway -> internetপ্যাকেট বেরিয়ে গেল, internet শুধু NAT-GW-এর public IP দেখছে
লক্ষ করুন — একটা মাত্র outbound packet চার-বার filter-check পার হয় (instance-এর নিজের SG, private-subnet-NACL, public-subnet-NACL — NAT gateway public subnet-এ থাকায়), আর দুই-বার route-table lookup পার হয়, তারপরই internet gateway পর্যন্ত পৌঁছায়। Reply ফিরে আসার পথে প্রতিটা ধাপ উল্টে যায় — কিন্তু security group-এর ধাপে কোনো নতুন rule-lookup লাগে না (statefulness), NACL-এর দুই ধাপেই আবার fresh, independent rule-check লাগে (statelessness)।
হাতে-কষা subnet-গণিতের যাচাই
ipv4-subnetting-cidr লেসনের ঠিক সেই পদ্ধতিতে যাচাই করি 10.0.4.0/24 (আমাদের public-c subnet) সঠিক কিনা:
VPC: 10.0.0.0/16
prefix ২৪-এ যাওয়া মানে তৃতীয় octet-এ পুরো ৮ bit network-part-এ যোগ হলো
তৃতীয় octet (৫ম subnet, index শুরু ০ থেকে হলে index ৪) = 4
network: 10.0.4.0 (AND করলে চতুর্থ octet সব শূন্য হয়ে যায়)
broadcast: 10.0.4.255 (host-bit সব এক — শেষ octet পুরোটাই host-part)
usable: 2^8 − 2 = 254 (10.0.4.1 থেকে 10.0.4.254)এই হিসাব উপরের টেবিলের সাথে হুবহু মেলে — কারণ এটা নতুন কোনো গণিত না, বরং prerequisite লেসনের সেই একই AND-operation, শুধু cloud-VPC-context-এ প্রয়োগ।
ছবিতে
VPC 10.0.0.0/16
┌───────────────────────────────────────────────────────────────┐
│ │
│ AZ ap-south-1a AZ ap-south-1b AZ ap-south-1c │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ public-a │ │ public-b │ │ public-c │ │
│ │ 10.0.0.0/24 │ │ 10.0.2.0/24 │ │ 10.0.4.0/24 │ │
│ │ rt: 0/0->IGW │ │ rt: 0/0->IGW │ │ rt: 0/0->IGW │ │
│ │ [NAT gateway] │ │ │ │ │ │
│ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │
│ │ │ │ │
│ ┌───────┴────────┐ ┌───────┴────────┐ ┌───────┴────────┐ │
│ │ private-a │ │ private-b │ │ private-c │ │
│ │ 10.0.1.0/24 │ │ 10.0.3.0/24 │ │ 10.0.5.0/24 │ │
│ │ rt: 0/0->NAT │ │ rt: 0/0->NAT │ │ rt: 0/0->NAT │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
└──────────────────────────────┬──────────────────────────────────┘
│
internet gateway (IGW)
│
internet subnet boundary (NACL এখানে বসে — subnet-level, STATELESS)
┌─────────────────────────────────────────────────────────────────┐
│ inbound rule-list (numbered) outbound rule-list (numbered) │
│ #100 ALLOW tcp/443 0.0.0.0/0 #100 ALLOW tcp/1024-65535 * │
│ #* DENY all #* DENY all │
│ │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ instance A │ │ instance B │ │
│ │ [security group: SG-A] │ │ [security group: SG-B] │ │
│ │ inbound: 443 ALLOW │ │ inbound: 5432 ALLOW │ │
│ │ (STATEFUL — response │ │ (STATEFUL — response │ │
│ │ auto-allowed) │ │ auto-allowed) │ │
│ └───────────────────────┘ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
security group = প্রতিটা instance-কে ঘিরে, individually
NACL = পুরো subnet-কে ঘিরে, সবার জন্য একই ruleনিজে চালিয়ে দেখুন
সততার সাথে — এই sandbox-এ কি সত্যিই একটা VPC বানানো সম্ভব?
container-networking লেসনের experiment অংশের মতো এখানেও সততার সাথে স্বীকার করা দরকার: এই লেসন একটা macOS sandbox-এ লেখা হচ্ছে, যেখানে কোনো cloud provider credential নেই, আর একটা প্রকৃত VPC তৈরি করার জন্য একটা বাস্তব AWS/GCP account লাগবে (আর নির্দিষ্ট resource-এর জন্য সামান্য হলেও খরচ হতে পারে, যেমন NAT gateway-এর ঘণ্টাপ্রতি চার্জ)।
which aws
aws --versionএই sandbox-এ সত্যিই চালিয়ে পাওয়া output:
aws not foundaws CLI-ই sandbox-এ ইনস্টল করা নেই — তাই VPC তৈরির প্রকৃত API call এই মুহূর্তে চালানো অসম্ভব। কিন্তু এর মানে এই না যে এই লেসনের কোনো দাবিই যাচাই করা যাবে না — VPC-র সবচেয়ে গুরুত্বপূর্ণ গাণিতিক অংশ, CIDR subdivision, সম্পূর্ণ provider-independent, আর ipv4-subnetting-cidr লেসনের precedent অনুসরণ করে আমরা Python-এর stdlib ipaddress module-কেই referee বানাতে পারি:
import ipaddress
vpc = ipaddress.ip_network('10.0.0.0/16')
subnets = list(vpc.subnets(new_prefix=24))
print(f'মোট possible /24 subnet: {len(subnets)}')
print(f'প্রথমটা: {subnets[0]}, শেষটা: {subnets[-1]}')
for s in subnets[:3]:
print(s, '-> network', s.network_address, 'broadcast', s.broadcast_address,
'usable', s.num_addresses - 2)
# alignment-নিয়ম যাচাই — misaligned base rejected হয় কিনা
try:
ipaddress.ip_network('10.0.0.64/24', strict=True)
except ValueError as e:
print('strict=True rejects misaligned base:', e)এই sandbox-এ সত্যিই চালিয়ে পাওয়া output (Python 3.9.6):
মোট possible /24 subnet: 256
প্রথমটা: 10.0.0.0/24, শেষটা: 10.0.255.0/24
10.0.0.0/24 -> network 10.0.0.0 broadcast 10.0.0.255 usable 254
10.0.1.0/24 -> network 10.0.1.0 broadcast 10.0.1.255 usable 254
10.0.2.0/24 -> network 10.0.2.0 broadcast 10.0.2.255 usable 254
strict=True rejects misaligned base: 10.0.0.64/24 has host bits setদুইটা জিনিস এই output নিশ্চিত করে: প্রথমত, উপরের concept সেকশনের হাতে-কষা টেবিল (public-a থেকে private-c) সঠিক ছিল — ipaddress module একই network/broadcast/usable বের করেছে। দ্বিতীয়ত, ipv4-subnetting-cidr লেসনের alignment-নিয়ম (subnet-এর শুরু-ঠিকানা নিজের মাপের multiple হতে হয়) এখানেও সত্যি — 10.0.0.64/24 একটা অবৈধ subnet-boundary, কারণ 64 কোনো /24-block-এর (256-এর multiple) শুরু-বিন্দু না।
cloud CLI বা account না থাকলেও VPC-র মূল গণিত — CIDR arithmetic — সম্পূর্ণ provider-independent এবং Python-এর stdlib দিয়েই নির্ভুলভাবে যাচাই করা যায়, কোনো cloud account বা খরচ ছাড়াই।
নিজে বানান
VPC CIDR Carver + Security-Group-vs-NACL Filter Simulator
- carve_subnets() — ipaddress.ip_network().subnets(new_prefix=...) দিয়ে একটা VPC CIDR-কে সমান-মাপের subnet-এ ভাগ করা, প্রতিটার network/broadcast/usable প্রিন্ট করা
- AZ ও public/private role অ্যাসাইন করে concept সেকশনের ছয়-subnet টেবিলটাই কোডে পুনরুৎপাদন করা
- Packet ক্লাস — proto/src/dst, আর flow_key()/reverse_flow_key() যা statefulness simulate করতে লাগবে
- SecurityGroup ক্লাস — শুধু inbound rule + একটা connection_table; outbound evaluate হয় reverse-flow-lookup দিয়ে, কোনো explicit outbound rule ছাড়াই
- NetworkACL ক্লাস — inbound ও outbound সম্পূর্ণ আলাদা rule-list; কোনো state/memory নেই
- একই inbound-request + outbound-response জোড়া দুই মডেলে চালিয়ে stateful-vs-stateless পার্থক্যটা বাস্তব output-এ দেখানো
এই প্রোগ্রামটাই এই লেসনের প্রধান “সত্যিই চালানো” প্রমাণ — পূর্ববর্তী experiment সেকশনে দেখানো হয়েছে বাস্তব cloud API call এই sandbox-এ সম্ভব না, কিন্তু pure Python-এ পুরো VPC CIDR-গণিত আর security-group/NACL-এর filtering-logic হুবহু বাস্তব semantics মেনে সিমুলেট করা যায়, কোনো external dependency ছাড়াই।
#!/usr/bin/env python3
"""
vpc_sim.py — দুইটা স্বাধীন অংশ:
(১) ipaddress module দিয়ে একটা VPC CIDR block-কে সমান-মাপের subnet-এ ভাগ করা
(২) security group (stateful) বনাম NACL (stateless) packet-filtering simulation
"""
import ipaddress
# ---------- অংশ ১: VPC CIDR carving ----------
def carve_subnets(vpc_cidr, new_prefix, how_many=None):
"""vpc_cidr-কে new_prefix-মাপের সমান subnet-এ ভাগ করে, প্রথম how_many-টা
(না দিলে সবগুলো) ফেরত দেয়। ipaddress.subnets() নিজেই CIDR-alignment মেনে চলে।"""
vpc = ipaddress.ip_network(vpc_cidr)
if new_prefix <= vpc.prefixlen:
raise ValueError(f"new_prefix ({new_prefix}) must exceed vpc prefix ({vpc.prefixlen})")
all_subnets = list(vpc.subnets(new_prefix=new_prefix))
chosen = all_subnets[:how_many] if how_many else all_subnets
return len(all_subnets), chosen
def describe(net):
hosts = list(net.hosts())
return {
"cidr": str(net), "network": str(net.network_address),
"broadcast": str(net.broadcast_address),
"first_usable": str(hosts[0]) if hosts else "-",
"last_usable": str(hosts[-1]) if hosts else "-",
"usable": len(hosts),
}
print("== অংশ ১: VPC 10.0.0.0/16 কে /24 subnet-এ ভাগ করা ==\n")
vpc_cidr = "10.0.0.0/16"
total_possible, subnets = carve_subnets(vpc_cidr, new_prefix=24, how_many=6)
print(f"VPC {vpc_cidr} থেকে /24-মাপের মোট সম্ভাব্য subnet: {total_possible}")
print(f"এই দৃশ্যকল্পে প্রথম {len(subnets)}-টা — ৩টা AZ, প্রতিটাতে ১ public + ১ private:\n")
roles = [("public-a", "ap-south-1a"), ("private-a", "ap-south-1a"),
("public-b", "ap-south-1b"), ("private-b", "ap-south-1b"),
("public-c", "ap-south-1c"), ("private-c", "ap-south-1c")]
for (role, az), net in zip(roles, subnets):
d = describe(net)
print(f"[{role:11s}] {az:12s} {d['cidr']:15s} "
f"network={d['network']:10s} broadcast={d['broadcast']:11s} "
f"usable={d['first_usable']}..{d['last_usable']} ({d['usable']} hosts)")
# ---------- অংশ ২: security group (stateful) বনাম NACL (stateless) ----------
print("\n== অংশ ২: security group (stateful) বনাম NACL (stateless) ==\n")
class Packet:
def __init__(self, proto, src_ip, src_port, dst_ip, dst_port):
self.proto, self.src_ip, self.src_port = proto, src_ip, src_port
self.dst_ip, self.dst_port = dst_ip, dst_port
def flow_key(self):
return (self.proto, self.src_ip, self.src_port, self.dst_ip, self.dst_port)
def reverse_flow_key(self):
# এই packet যদি কোনো পূর্ববর্তী inbound request-এর *reply* হয়,
# সেই request-এর flow_key() ঠিক এটাই হতো
return (self.proto, self.dst_ip, self.dst_port, self.src_ip, self.src_port)
def __repr__(self):
return f"{self.proto} {self.src_ip}:{self.src_port} -> {self.dst_ip}:{self.dst_port}"
def port_match(lo, hi, port):
return lo <= port <= hi
class SecurityGroup:
"""Instance-level, STATEFUL। শুধু inbound rule — matching response
automatically অনুমোদিত হয়, কোনো outbound rule ছাড়াই।"""
def __init__(self, name, inbound_rules):
self.name, self.inbound_rules = name, inbound_rules
self.connection_table = set()
def evaluate_inbound(self, pkt):
allowed = any(pkt.proto == p and port_match(lo, hi, pkt.dst_port)
for p, lo, hi in self.inbound_rules)
if allowed:
self.connection_table.add(pkt.flow_key())
return allowed
def evaluate_outbound(self, pkt):
# কোনো outbound rule নেই — connection_table-এ matching reverse-flow
# আছে কি না দেখাই statefulness-এর সংজ্ঞা
return pkt.reverse_flow_key() in self.connection_table
class NetworkACL:
"""Subnet-level, STATELESS। inbound/outbound সম্পূর্ণ আলাদা, কোনো memory নেই।"""
def __init__(self, name, inbound_rules, outbound_rules):
self.name = name
self.inbound_rules, self.outbound_rules = inbound_rules, outbound_rules
def evaluate_inbound(self, pkt):
return any(pkt.proto == p and port_match(lo, hi, pkt.dst_port)
for p, lo, hi in self.inbound_rules)
def evaluate_outbound(self, pkt):
# outbound rule-এর port-range মিলে packet-এর destination port-এর
# সাথে — server-এর নিজের src-port ৪৪৩-এর সাথে না
return any(pkt.proto == p and port_match(lo, hi, pkt.dst_port)
for p, lo, hi in self.outbound_rules)
def run_case(label, request, response, sg, nacl_no_out, nacl_with_out):
print(f"--- {label} ---")
print(f" inbound request : {request}")
print(f" outbound response: {response}\n")
print(f" [security group '{sg.name}'] inbound -> "
f"{'ALLOW' if sg.evaluate_inbound(request) else 'DENY'}")
print(f" [security group '{sg.name}'] outbound -> "
f"{'ALLOW' if sg.evaluate_outbound(response) else 'DENY'}"
f" (connection_table-এ matching entry, আলাদা rule ছাড়াই)")
print(f" [NACL '{nacl_no_out.name}'] inbound -> "
f"{'ALLOW' if nacl_no_out.evaluate_inbound(request) else 'DENY'}")
print(f" [NACL '{nacl_no_out.name}'] outbound -> "
f"{'ALLOW' if nacl_no_out.evaluate_outbound(response) else 'DENY'}"
f" (কোনো outbound rule নেই -> reply বেরোতে পারবে না)")
print(f" [NACL '{nacl_with_out.name}'] inbound -> "
f"{'ALLOW' if nacl_with_out.evaluate_inbound(request) else 'DENY'}")
print(f" [NACL '{nacl_with_out.name}'] outbound -> "
f"{'ALLOW' if nacl_with_out.evaluate_outbound(response) else 'DENY'}"
f" (explicit ephemeral-range outbound rule বসানো -> এবার allow)")
print()
request = Packet("tcp", "203.0.113.5", 54321, "10.0.0.5", 443)
response = Packet("tcp", "10.0.0.5", 443, "203.0.113.5", 54321)
web_sg = SecurityGroup("web-sg", inbound_rules=[("tcp", 443, 443)])
broken_nacl = NetworkACL("subnet-nacl (misconfigured)",
inbound_rules=[("tcp", 443, 443)], outbound_rules=[])
fixed_nacl = NetworkACL("subnet-nacl (fixed)",
inbound_rules=[("tcp", 443, 443)],
outbound_rules=[("tcp", 1024, 65535)])
run_case("HTTPS request + তার reply", request, response, web_sg, broken_nacl, fixed_nacl)
spoofed = Packet("tcp", "10.0.0.5", 443, "198.51.100.9", 9999)
print("--- একটা outbound packet যেটা আসলে কোনো প্রকৃত request-এর জবাব না ---")
print(f" outbound packet: {spoofed}\n")
print(f" [security group 'web-sg'] outbound -> "
f"{'ALLOW' if web_sg.evaluate_outbound(spoofed) else 'DENY'}"
f" (এই reverse-flow-এর কোনো connection_table entry নেই -> block)")
print(f" [NACL 'subnet-nacl (fixed)'] outbound -> "
f"{'ALLOW' if fixed_nacl.evaluate_outbound(spoofed) else 'DENY'}"
f" (stateless -> শুধু port-range মিললেই allow, কে request করেছিল তা কখনো চেক হয় না)")এই sandbox-এ সত্যিই চালিয়ে পাওয়া output (macOS-এ Python 3.9.6):
== অংশ ১: VPC 10.0.0.0/16 কে /24 subnet-এ ভাগ করা ==
VPC 10.0.0.0/16 থেকে /24-মাপের মোট সম্ভাব্য subnet: 256
এই দৃশ্যকল্পে প্রথম 6-টা — ৩টা AZ, প্রতিটাতে ১ public + ১ private:
[public-a ] ap-south-1a 10.0.0.0/24 network=10.0.0.0 broadcast=10.0.0.255 usable=10.0.0.1..10.0.0.254 (254 hosts)
[private-a ] ap-south-1a 10.0.1.0/24 network=10.0.1.0 broadcast=10.0.1.255 usable=10.0.1.1..10.0.1.254 (254 hosts)
[public-b ] ap-south-1b 10.0.2.0/24 network=10.0.2.0 broadcast=10.0.2.255 usable=10.0.2.1..10.0.2.254 (254 hosts)
[private-b ] ap-south-1b 10.0.3.0/24 network=10.0.3.0 broadcast=10.0.3.255 usable=10.0.3.1..10.0.3.254 (254 hosts)
[public-c ] ap-south-1c 10.0.4.0/24 network=10.0.4.0 broadcast=10.0.4.255 usable=10.0.4.1..10.0.4.254 (254 hosts)
[private-c ] ap-south-1c 10.0.5.0/24 network=10.0.5.0 broadcast=10.0.5.255 usable=10.0.5.1..10.0.5.254 (254 hosts)
== অংশ ২: security group (stateful) বনাম NACL (stateless) ==
--- HTTPS request + তার reply ---
inbound request : tcp 203.0.113.5:54321 -> 10.0.0.5:443
outbound response: tcp 10.0.0.5:443 -> 203.0.113.5:54321
[security group 'web-sg'] inbound -> ALLOW
[security group 'web-sg'] outbound -> ALLOW (connection_table-এ matching entry, আলাদা rule ছাড়াই)
[NACL 'subnet-nacl (misconfigured)'] inbound -> ALLOW
[NACL 'subnet-nacl (misconfigured)'] outbound -> DENY (কোনো outbound rule নেই -> reply বেরোতে পারবে না)
[NACL 'subnet-nacl (fixed)'] inbound -> ALLOW
[NACL 'subnet-nacl (fixed)'] outbound -> ALLOW (explicit ephemeral-range outbound rule বসানো -> এবার allow)
--- একটা outbound packet যেটা আসলে কোনো প্রকৃত request-এর জবাব না ---
outbound packet: tcp 10.0.0.5:443 -> 198.51.100.9:9999
[security group 'web-sg'] outbound -> DENY (এই reverse-flow-এর কোনো connection_table entry নেই -> block)
[NACL 'subnet-nacl (fixed)'] outbound -> ALLOW (stateless -> শুধু port-range মিললেই allow, কে request করেছিল তা কখনো চেক হয় না)তিনটা ফলাফল বিশেষভাবে লক্ষ করার মতো, তিনটাই এই sandbox-এ সত্যিই দেখা গেছে:
প্রথম — একই inbound-request/outbound-response জোড়ায় security group সবসময় সঠিকভাবে reply allow করেছে, কোনো outbound rule ছাড়াই — statefulness-এর সংজ্ঞাই এখানে বাস্তব output।
দ্বিতীয় — misconfigured NACL (inbound rule আছে, outbound rule ফাঁকা)-এ inbound ঠিকঠাক ঢুকেছে কিন্তু outbound reply DENY হয়েছে — এটাই সেই বাস্তব connection-breaking bug যা এই লেসনের concept সেকশনে বর্ণনা করা হয়েছিল, এখন প্রমাণিত output-এ।
তৃতীয়, সবচেয়ে গুরুত্বপূর্ণ — একটা সম্পূর্ণ ভুয়া, কোনো request-এর জবাব-না-হওয়া outbound packet security group ঠিকই block করেছে (কোনো connection_table entry নেই), কিন্তু “fixed” NACL সেটাকেও ALLOW করে দিয়েছে, শুধু কারণ destination port 9999-ও ephemeral range-এর ভেতরে পড়ে। এটাই stateless firewall-এর মূল দুর্বলতার একটা বাস্তব, executed প্রমাণ: port-range-ভিত্তিক outbound rule শুধু বৈধ reply-ই না, port-criteria-মেলা যেকোনো traffic পাস করিয়ে দেয় — কারণ NACL কখনো জানেই না কে আসলে request করেছিল।
নিজে বাড়ান:
NetworkACL-এ rule-numbering আর “প্রথম-match-জেতে” evaluation যোগ করুন (এখন সব ALLOW-rule-এর union হিসেবে কাজ করছে) — একটা DENY rule-ও যোগ করে দেখান সেটা কীভাবে একটা পরের ALLOW rule-কে override করে- একটা
SymmetricSecurityGroupvariant বানান যেখানে একাধিক security group একসাথে একটা instance-এ থাকতে পারে (effective rule-set = union), আর দুইটা আলাদা SG-র rule মিলিয়ে একটা connection allow হওয়া দেখান - NAT gateway যোগ করুন — private subnet থেকে internet-এ একটা packet পাঠিয়ে দেখান route-table lookup, NAT rewrite, আর ফেরত-আসা reply-র পুরো hop-trace (এই লেসনের LayerTrace-এর executable রূপ)
বাস্তব সিস্টেমে
বাস্তব সিস্টেমে — VPC, subnet, security group কোথায় কাজ করছে
-
প্রায় প্রতিটা production cloud workload-ই একটা VPC-র ভেতরে চলে। AWS EC2, RDS, EKS — সবকিছুই একটা VPC-subnet-এ বসে; GCP-এর সমতুল্য নাম “VPC network”, Azure-এর “Virtual Network (VNet)” — নাম আলাদা, ধারণা হুবহু এক (এই লেসনের GCP reference দেখুন)।
-
3-tier architecture-এর subnet-layout। একটা classic web-app-এ: load balancer public subnet-এ, application server private subnet-এ (শুধু load balancer থেকে inbound allow), database আরও একটা private subnet-এ (শুধু application server থেকে inbound allow, কোনো internet route-ই নেই, NAT gateway route-ও না — সম্পূর্ণ isolated)। এই লেসনের security-group scoping ঠিক এই layered-access-control-এর ভিত্তি।
-
Security group-কে security-group দিয়েই reference করা। বাস্তবে security-group rule প্রায়ই একটা IP-range না, বরং অন্য একটা security-group-id-কে source হিসেবে নেয় — “শুধু app-server-SG-তে যা আছে তাদের কাছ থেকে database-এ 5432-এ inbound allow করো”। Instance-দের IP বদলে গেলেও এই rule ঠিক থাকে, কারণ এটা IP-নির্ভর না, group-membership-নির্ভর।
-
NAT gateway-এর বিল। AWS-এ NAT gateway-র চার্জ ঘণ্টাপ্রতি + per-GB-processed — একটা architecture-review-এ “কেন private subnet?” প্রশ্নের উত্তর নিরাপত্তা হলে, মনে রাখা দরকার সেই একই isolation security-group দিয়েও (public IP ছাড়া) অনেকটা পাওয়া যায়; NAT gateway-এর অতিরিক্ত খরচ আসলে address-hiding + outbound-access-এর জন্য,
networking/natলেসনের সেই একই trade-off cloud-বিলে প্রতিফলিত। -
VPC peering ও Transit Gateway। দুইটা আলাদা VPC-র মধ্যে সরাসরি (internet না ঘুরে) যোগাযোগ দরকার হলে VPC peering বা Transit Gateway ব্যবহার হয় — route table-এ শুধু আরেকটা target-type যোগ হয় (
0.0.0.0/0না, একটা নির্দিষ্ট peer-VPC-CIDR পাঠানো হয় peering-connection-এর দিকে) — এই লেসনের route-table মডেলেরই সম্প্রসারণ, নতুন কোনো concept না। -
Kubernetes-এর সাথে সরাসরি সংযোগ।
container-networkingওkubernetes-architectureলেসনের CNI plugin-গুলোর মধ্যে যেগুলো cloud-native (AWS VPC CNI, GCP-এর native routing) — তারা প্রতিটা Pod-কে সরাসরি VPC-এর subnet থেকে একটা real, routable IP দেয়, যার মানে সেই Pod-এর traffic-ও ঠিক এই লেসনের security-group/NACL/route-table দিয়েই filter হয় — cluster-networking আর VPC-networking একই layer-এ মিশে যায়।
যে ভুলগুলো সবাই করে
“একটা subnet 'public' বা 'private' — এটা তার নিজস্ব একটা builtin property, VPC তৈরির সময়ই স্থায়ীভাবে ঠিক হয়ে যায়।”
সম্পূর্ণ ভুল, আর এই লেসনের সবচেয়ে গুরুত্বপূর্ণ সংশোধন। “public”/“private” কোনো subnet-এর নিজস্ব flag বা attribute না — এটা শুধুমাত্র সেই subnet-এর সাথে যুক্ত route table-এর একটা ফলাফল, নির্দিষ্টভাবে 0.0.0.0/0 কোথায় যাচ্ছে তার উপর নির্ভর করে। একটা subnet-কে public থেকে private বানাতে (বা উল্টো) তার CIDR, IP range, বা availability-zone কিছুই বদলাতে হয় না — শুধু তার route-table-association বদলে দিলেই হয়। এই কারণেই দুইটা ভিন্ন subnet একই CIDR-মাপ, একই AZ, একই সবকিছু নিয়ে থেকেও একটা public আরেকটা private হতে পারে — পার্থক্য শুধু route table-এর একটা লাইনে।
“Security group আর NACL মূলত একই জিনিস — দুইটা layer-এ redundant security, যেকোনো একটা রাখলেই যথেষ্ট।”
দুইটাই “firewall” শব্দে ফেলা যায় ঠিকই, কিন্তু তাদের behavior দুইটা সম্পূর্ণ ভিন্ন axis-এ আলাদা। Security group instance-level, stateful, শুধু-allow — এটা fine-grained, per-resource access control-এর জন্য উপযুক্ত (যেমন “শুধু app-server-SG থেকে database-এ inbound”)। NACL subnet-level, stateless, allow+deny দুটোই — এটা coarse-grained, “পুরো subnet-এর জন্য একটা emergency block” টাইপ সিদ্ধান্তের জন্য উপযুক্ত (যেমন একটা নির্দিষ্ট malicious IP-range পুরো subnet থেকে deny করা, যা security group দিয়ে প্রতিটা instance-এ আলাদাভাবে করতে হতো)। দুটো layer redundant না — তারা ভিন্ন সমস্যার সমাধান করে, আর defense-in-depth-এর জন্য বাস্তবে দুটোই একসাথে ব্যবহৃত হয়।
“NAT gateway VPC-এর ভেতরের traffic-কেও 'secure' বা 'private' করে রাখে — এটা একটা security device, তাই আলাদা security group/NACL-এর দরকার কমে যায়।”
এই ভুলটা ঠিক networking/nat লেসনের “NAT = security” misconception-এরই cloud-সংস্করণ। NAT gateway-এর একমাত্র কাজ address translation — private subnet-এর resource-দের outbound-only internet access দেওয়া। এটা কোনো firewall না, কোনো traffic-filtering rule evaluate করে না — এটা শুধু rewrite করে আর mapping রাখে। protection-টা (কোনটা inbound allow হবে, কোনটা না) সম্পূর্ণভাবে security group আর NACL-এর কাজ, NAT gateway-এর না। NAT gateway সরিয়ে ফেললেও, বা না থাকলেও, security group/NACL-এর প্রয়োজনীয়তা একটুও কমে না।
“VPC-র জন্য বড় CIDR block (যেমন /16) নিলে খরচ বাড়ে, বা সেই সব address 'ব্যবহৃত' হয়ে যায়।”
ipv4-subnetting-cidr লেসনের সেই একই সত্য এখানেও প্রযোজ্য: CIDR block শুধু address space সংরক্ষণ করে, allocate বা “ব্যবহার” করে না। একটা /16 VPC নিলে তার ৬৫,৫৩৬টা ঠিকানার কোনো cost নেই যতক্ষণ না সেগুলোর ভেতরে আসলে subnet তৈরি হয় আর সেই subnet-এ resource বসে। এই কারণেই বাস্তবে প্রায় সবসময় বড় CIDR block-ই নেওয়া হয় — খালি জায়গা রাখা “বিনামূল্যে বীমা”, ভবিষ্যতের নতুন subnet বা region-expansion-এর জন্য।
বুঝেছেন কি না দেখুন
1একটা subnet public না private, সেটা ঠিক কীসের ভিত্তিতে নির্ধারিত হয়? subnet-এর CIDR বা availability zone কি এই সিদ্ধান্তে কোনো ভূমিকা রাখে?
স্মরণ
পুরোপুরি সেই subnet-এর সাথে যুক্ত route table-এর ভিত্তিতে — নির্দিষ্টভাবে 0.0.0.0/0 destination-এর target কী তার উপর। 0.0.0.0/0 -> internet gateway মানে public; 0.0.0.0/0 -> NAT gateway মানে private-with-outbound-access; কোনো 0.0.0.0/0 route-ই না থাকলে (শুধু local) সম্পূর্ণ isolated। CIDR block বা availability zone-এর সাথে এই সিদ্ধান্তের কোনো সম্পর্ক নেই — একই CIDR-মাপ, একই AZ-এর দুইটা subnet ভিন্ন route-table-association-এ ভিন্ন classification পেতে পারে।
2VPC 10.0.0.0/16-কে /24 subnet-এ ভাগ করা হয়েছে, তৃতীয় octet অনুযায়ী ক্রমানুসারে। সপ্তম subnet-এর (index অনুযায়ী তৃতীয় octet 6) network address, broadcast address, আর usable host count কী হবে?
প্রয়োগ
10.0.0.0/16-কে /24 subnet-এ ভাগ করা হয়েছে, তৃতীয় octet অনুযায়ী ক্রমানুসারে। সপ্তম subnet-এর (index অনুযায়ী তৃতীয় octet 6) network address, broadcast address, আর usable host count কী হবে?Network: 10.0.6.0 (তৃতীয় octet সরাসরি 6, চতুর্থ octet সব শূন্য — host-part পুরো চতুর্থ octet)। Broadcast: 10.0.6.255 (host-bit সব এক)। Usable count: 2^8 − 2 = 254 (10.0.6.1 থেকে 10.0.6.254)। এটা ঠিক ipv4-subnetting-cidr লেসনের সেই একই AND/OR-operation-ভিত্তিক হিসাব, শুধু এখানে VPC-context-এ প্রয়োগ করা হয়েছে।
3একটা web-server-এর security group-এ শুধু “TCP 443 inbound allow” rule আছে, কোনো outbound rule নেই — তবু এটা ঠিকভাবে কাজ করে। কিন্তু একটা NACL-এ যদি একই রকম “শুধু inbound rule, outbound খালি” রাখা হয়, connection ভেঙে যায় কেন? এই দুইয়ের behavior-এর পার্থক্যের মূল কারণ কী?
যুক্তি
মূল কারণ statefulness-এর পার্থক্য। Security group stateful — inbound request allow হওয়ার সময় সেই flow-এর একটা entry connection-tracking টেবিলে সংরক্ষিত হয়, আর reply আসার সময় সেই টেবিলে matching reverse-flow পাওয়া গেলে outbound automatically allow হয়ে যায়, কোনো আলাদা rule না লিখেই। NACL stateless — কোনো connection-tracking টেবিল নেই, প্রতিটা packet (inbound হোক বা outbound) সম্পূর্ণ স্বাধীনভাবে বিচার হয় শুধু rule-list দেখে। তাই inbound request ভেতরে ঢুকলেও, reply (একটা নতুন, স্বাধীন outbound packet, ভিন্ন src/dst) matching কোনো outbound rule না পেলে ব্লক হয়ে যায় — connection ভাঙে, যদিও original request একদম ঠিকভাবে ঢুকেছিল।
4একটা NACL-এ inbound rule “TCP 80 allow” আছে, আর outbound rule-এ শুধু “TCP port 80 allow” (ephemeral range না) লেখা আছে। একটা client 203.0.113.9:60000 থেকে server 10.0.0.8:80-এ request পাঠাল। request-টা কি ভেতরে ঢুকবে? server-এর reply (src=10.0.0.8:80, dst=203.0.113.9:60000) কি বেরোতে পারবে?
প্রয়োগ
203.0.113.9:60000 থেকে server 10.0.0.8:80-এ request পাঠাল। request-টা কি ভেতরে ঢুকবে? server-এর reply (src=10.0.0.8:80, dst=203.0.113.9:60000) কি বেরোতে পারবে?Request ঢুকবে — inbound rule dst-port 80-এর সাথে মেলে। কিন্তু reply বেরোতে পারবে না। NACL-এর outbound rule-এর port-range মিলে packet-এর destination port-এর সাথে, আর reply packet-এর destination port হলো client-এর ephemeral port (60000), server-এর নিজের port (80) না। outbound rule শুধু port 80-কে allow করছে, 60000-কে না — তাই reply ব্লক হয়ে যাবে। সঠিক fix হতো outbound rule-এ ephemeral range (1024–65535) allow করা, port 80 না।
5Build সেকশনের simulation-এ দেখা গেছে, একটা “fixed” NACL (outbound ephemeral-range allow করা) একটা সম্পূর্ণ ভুয়া packet-ও (কোনো প্রকৃত request-এর reply না) ALLOW করে দিয়েছে, অথচ security group সেই একই packet ঠিকই block করেছে। এই পার্থক্যটার নিরাপত্তাগত তাৎপর্য কী?
যুক্তি
এটা দেখায় stateless outbound rule একটা coarse-grained, broad-brush সমাধান — এটা শুধু “এই port-range-এ যাওয়া সবকিছু allow” বলতে পারে, কিন্তু কখনো যাচাই করতে পারে না যে এই outbound packet-টা আসলে কোনো বৈধ, পূর্বে-ঢোকা inbound request-এর প্রকৃত reply কি না। একটা compromised instance বা misconfigured process এই একই ephemeral-range-এর সুযোগ নিয়ে যেকোনো external destination-এ traffic পাঠাতে পারবে, যতক্ষণ destination port ঐ range-এর ভেতরে পড়ে। Security group এই ফাঁকটা বন্ধ করে, কারণ এর allow-decision নির্ভর করে একটা প্রকৃত, ট্র্যাক-করা flow-এর অস্তিত্বের উপর, শুধু port-number-এর উপর না — এটাই stateful filtering-এর নিরাপত্তাগত সুবিধা, শুধু সুবিধাজনক না।
6একটা 3-tier application ডিজাইন করছেন: web (public subnet), app (private subnet), database (আরেকটা private subnet)। security group ও route-table-এর দিক থেকে সিদ্ধান্তগুলো সাজান — কোন tier কোন tier-এর সাথে কথা বলতে পারবে, আর database-subnet-এর route table-এ কী থাকা উচিত, কী থাকা উচিত না, এবং কেন।
ডিজাইন
Security group chain: web-sg-এ inbound 0.0.0.0/0-থেকে 443 (public traffic); app-sg-এ inbound শুধু web-sg থেকে (application port-এ, যেমন 8080) — IP-range না, security-group-id হিসেবে reference করে, যাতে web-tier-এর instance বদলালেও rule ঠিক থাকে; db-sg-এ inbound শুধু app-sg থেকে (5432/3306)। প্রতিটা tier শুধু ঠিক তার আগের tier-এর কাছ থেকেই inbound allow করে — কোনো tier সরাসরি বাইরে থেকে বা দুই-স্তর-দূরের tier থেকে পৌঁছানো যায় না (least-privilege)। Route table: web-subnet public (0.0.0.0/0 -> IGW); app-subnet private-with-outbound (0.0.0.0/0 -> NAT gateway, যদি app-layer-এর বাইরের API-তে কল করা লাগে); কিন্তু database-subnet-এ কোনো 0.0.0.0/0 route-ই থাকা উচিত না — শুধু local route, NAT gateway-এর দিকেও না। কারণ: database-এর কখনোই internet-এ কিছু বলার দরকার নেই (কোনো বৈধ ব্যবহার-ক্ষেত্র নেই), তাই 0.0.0.0/0-এর যেকোনো route (IGW হোক বা NAT) একটা অপ্রয়োজনীয় attack-surface — data-exfiltration বা compromised-instance-এর বাইরের সাথে যোগাযোগের একটা সুযোগ, যেটা ঘটে যাওয়ার আগেই route-table-স্তরে বন্ধ করে দেওয়া উচিত।
এরপর কী
এরপর কী
আজ আমরা VPC, subnet, route table, আর security group — এই পুরো ব্যবস্থাটাই বর্ণনা করেছি prose-এ: “একটা VPC তৈরি করুন 10.0.0.0/16 দিয়ে, তারপর ছয়টা subnet, তারপর route table-এ এই লাইনগুলো যোগ করুন, তারপর security group-এ এই rule-গুলো বসান।” বাস্তব practice-এ কেউই এই পুরো কাজটা প্রতিবার হাতে, একটা web console-এ ক্লিক করে করে না — বিশেষত একটা production environment-এর জন্য, যেখানে এই একই setup বহুবার, ভিন্ন environment-এ (dev, staging, prod), হুবহু নির্ভুলভাবে পুনরাবৃত্তি করতে হয়।
এই সমস্যার সমাধানের নাম Infrastructure as Code — VPC, subnet, route table, security group-এর মতো প্রতিটা resource-কে prose বা click-sequence হিসেবে না, বরং একটা declarative কোড-ফাইলে বর্ণনা করা: “আমি চাই ঠিক এই CIDR-এর একটা VPC থাকুক, ঠিক এই ছয়টা subnet-সহ” — আর একটা টুল সেই declaration পড়ে বুঝে নেয় ঠিক কোন API-call-গুলো লাগবে সেই অবস্থায় পৌঁছাতে।
পরের লেসনে, virtualization-cloud/infrastructure-as-code-terraform:
- Declarative বনাম imperative provisioning — পার্থক্যটা ঠিক কোথায়
- Terraform-এর state file, plan/apply চক্র, আর resource-graph
- আজকের VPC/subnet/route-table/security-group-কেই একটা
.tfফাইলে লেখা, hand-computed CIDR-এর জায়গায়cidrsubnet()-জাতীয় function ব্যবহার - Idempotency — একই কোড দুইবার apply করলে কেন কিছুই বদলায় না, প্রথমবার সব বদলে যাওয়ার পর
আজকের route-table-এর সেই এক লাইন যা একটা subnet-কে public বানায়, পরের লেসনে হয়ে উঠবে ঠিক এক লাইনের Terraform কোড — শুধু prose-এর বদলে declarative syntax-এ লেখা।
আরও পড়ুন
- What Is Amazon VPC? — Amazon VPC User Guide · VPC-এর প্রামাণ্য সংজ্ঞা, CIDR block সীমা (/16 থেকে /28), আর subnet/route-table/gateway-এর সরকারি vocabulary
- Control Traffic to Resources Using Security Groups — Amazon VPC User Guide · Security group-এর stateful আচরণ, instance-level scope, আর default-deny/allow-only rule model-এর প্রামাণ্য বিবরণ
- Control Traffic to Subnets Using Network ACLs — Amazon VPC User Guide · NACL-এর stateless আচরণ, subnet-level scope, numbered rule evaluation, আর inbound/outbound আলাদা rule-list-এর প্রামাণ্য বিবরণ
- Virtual Private Cloud (VPC) network overview — Google Cloud Documentation · একটা দ্বিতীয় cloud provider-এর VPC মডেল — দেখায় যে এই লেসনের ধারণাগুলো কোনো একক-vendor quirk না, বরং cloud networking-এর সাধারণ প্যাটার্ন