<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shoban Chiddarth</title>
    <description>The latest articles on DEV Community by Shoban Chiddarth (@shobanchiddarth).</description>
    <link>https://dev.to/shobanchiddarth</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3764440%2Fd3ec9df1-b9a8-4982-bce2-0a6e52ec602f.jpg</url>
      <title>DEV Community: Shoban Chiddarth</title>
      <link>https://dev.to/shobanchiddarth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shobanchiddarth"/>
    <language>en</language>
    <item>
      <title>I did an ettercap MITM Home Lab</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Mon, 06 Jul 2026 17:15:06 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/i-did-an-ettercap-mitm-home-lab-3aml</link>
      <guid>https://dev.to/shobanchiddarth/i-did-an-ettercap-mitm-home-lab-3aml</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As I've been getting deeper into Cybersecurity and after the &lt;a href="https://github.com/ShobanChiddarth/siem-home-lab-wazuh" rel="noopener noreferrer"&gt;Wazuh lab&lt;/a&gt; I did a MITM lab using ettercap for ARP Poisoning and Wireshark to capture the traffic and analyse it.&lt;/p&gt;

&lt;p&gt;Full write up on &lt;a href="https://github.com/ShobanChiddarth/ettercap-mitm-home-lab" rel="noopener noreferrer"&gt;GitHub repo.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo video: &lt;a href="https://youtu.be/jZ10v4bCBzY" rel="noopener noreferrer"&gt;youtu.be/jZ10v4bCBzY&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About the lab
&lt;/h2&gt;

&lt;p&gt;I am enrolled in a college course for Cybersecurity and now the course is currently teaching us Penetration Testing. And this exact lab was taught to us in Network Security, a few days ago.&lt;/p&gt;

&lt;p&gt;That one was very similar to this one but the network architecture was very different. We were told to connect our laptops to our mobile hotspot (so that we are hacking in a network we own, completely legal) and put one Kali VM in bridged adapter mode.&lt;/p&gt;

&lt;p&gt;Now both the host and the Kali VM behave like real network devices talking to the real router (phone). We were told to consider the host as the victim and Kali as the attacker.&lt;/p&gt;

&lt;p&gt;And the ettercap command was run in Kali to make the host think Kali is the router and to make the router think Kali is the host, then we were told to generate some traffic on the host, and open that same vulnerable website mentioned in the write up and capture it in wireshark and login. Then after that we were supposed to do the same analysis and sniff out the credentials.&lt;/p&gt;

&lt;p&gt;In theory it sounds like it works and it did work for a lot of people using Windows on their host machine but for me, since I use Linux btw, the traffic from the host was just not being seen from Kali even after disabling Cloudflare WARP. I called my teacher and showed him and he said it could be because of some inbuilt security features in Linux.&lt;/p&gt;

&lt;p&gt;So I introduced the &lt;a href="https://dev.to/shobanchiddarth/the-superior-way-to-make-vms-communicate-with-each-other-as-well-as-host-with-internet-access-42m1"&gt;lab architecture&lt;/a&gt; I usually use when working with multiple Virtual Machines because this one is just cleaner and more isolated. And I cloned a Windows VM from &lt;a href="https://www.linkedin.com/posts/shobanchiddarth_homelab-cybersecurity-virtualbox-ugcPost-7478320739788464128-C_x3/" rel="noopener noreferrer"&gt;my list of base images&lt;/a&gt; into the LAN part of the pfSense router and then I did the same thing on Kali with the Windows hosts IP and it worked, the traffic capture and analysis and credential sniffing.&lt;/p&gt;

&lt;p&gt;So I thought it will never work with Linux but if you read the write up, I never used Windows anywhere and replaced the Windows VM with Debian Desktop VMs for victims. And it still worked.&lt;/p&gt;

&lt;p&gt;Turns out, it will only not work when the Linux host is being ARP poisoned by a bridged VM. When Linux is not the host and a VM on the same network as the attacker, ARP poisoning will work just fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I learnt from this lab
&lt;/h2&gt;

&lt;p&gt;I learnt about &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ARP poisoning&lt;/li&gt;
&lt;li&gt;precisely how attackers sniff out passwords over plain HTTP connections&lt;/li&gt;
&lt;li&gt;and why exactly ARP snooping is required in enterprises, something I learned while studying for CCNA.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I also documented the remediation steps for individuals, network administrators and website owners to protect themselves from this type of attack. It is in the write up, link above, go read it.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
      <category>arp</category>
    </item>
    <item>
      <title>I finished the SIEM Wazuh Home lab</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Sat, 04 Jul 2026 09:54:02 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/i-finished-the-siem-wazuh-home-lab-50a4</link>
      <guid>https://dev.to/shobanchiddarth/i-finished-the-siem-wazuh-home-lab-50a4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Continuing from &lt;a href="https://dev.to/shobanchiddarth/day-1-of-cybersecurity-4gdm"&gt;this post&lt;/a&gt;, the SIEM Wazuh lab is done.&lt;/p&gt;

&lt;p&gt;You can check it out here: &lt;a href="https://github.com/ShobanChiddarth/siem-home-lab-wazuh" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/X-zppQ0WpaU" rel="noopener noreferrer"&gt;Demo Video&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reducing the scope
&lt;/h2&gt;

&lt;p&gt;I dropped pfSense as a log source because setting up syslog on network devices was never a requirement for this lab as well as the bigger project I am building. This is something that will be managed by security analysts, and also there is a limitation with that as everything that will hit :514 will be collected under agent &lt;code&gt;000&lt;/code&gt; so differentiating between network devices that send logs would be another hectic job. &lt;/p&gt;

&lt;p&gt;I just decided not to worry about that and set up Wazuh agents on Windows and Debian and did File Integrity Monitoring, sudo to ROOT privilege escalation monitoring, and SSH brute force detection. I also filtered the logs by providing queries, which is the core focus of the lab for the bigger project I am building. For details on all of that, you can check out the GitHub repo I shared earlier.&lt;/p&gt;

&lt;p&gt;Now the network architecture would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fktc6iualwkkxj5ahmwr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fktc6iualwkkxj5ahmwr3.png" alt="network-architecture-3" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reducing the size of VMs for video recording
&lt;/h2&gt;

&lt;p&gt;I had to reduce the RAM of Kali VM from 12 GB to 4 GB, and Windows from 8 GB to 4 GB. Now if I run all VMs at once without Brave Browser, VSCodium and Obsidian, I would have enough RAM left to run OBS in order to do the demo video and capture it. &lt;/p&gt;

&lt;p&gt;Fortunately my PC did not hang and I was able to record it in OBS, edit and render it in Kdenlive for publishing on YouTube. I didn't do any advanced video editing, just basic trimming and merging and then I published it on YouTube (link above).&lt;/p&gt;

&lt;h2&gt;
  
  
  Future of this project
&lt;/h2&gt;

&lt;p&gt;After submitting all these in the Monday weekly review I am gonna do the exact same thing on AWS using Terraform. Except I won't have a Windows EC2 instance, that is costly and not available for free tier.&lt;/p&gt;

&lt;p&gt;In the AWS Hosted lab I plan to set up a web server and make wazuh agent on the webserver monitor the webserver logs as well, and host a vulnerable web application and attack it with Kali. Then the logs for each type of attack will be on Wazuh dashboard, another server will host Wazuh server. And there will be another EC2 which acts as a user's desktop but is not really a desktop.&lt;/p&gt;

&lt;p&gt;The reason for hosting the lab in AWS is, we wan't a one click deploy button to spin up the lab from any computer in the world. The core idea of the project is to build on top of a lab. So we need to make the spinning up of the lab as seamless as possible.&lt;/p&gt;

&lt;p&gt;After that a chatbot interface making use of Wazuh API to write and query logs can be built and easily integrated into the existing lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;My first 100% pure Cybersecurity project is done. All other projects I did touched Cybersecurity to some extent, some more and some less. This is the first project that is purely Cybersecurity that I have done. I now understand Cybersecurity deeper than I did earlier. And I will do more.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
      <category>homelab</category>
      <category>wazuh</category>
    </item>
    <item>
      <title>Day 1 of Cybersecurity</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Fri, 03 Jul 2026 09:54:28 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/day-1-of-cybersecurity-4gdm</link>
      <guid>https://dev.to/shobanchiddarth/day-1-of-cybersecurity-4gdm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The title of the blog is clickbait and it is not actually my day 1 of Cybersecurity. I have been using Linux as on my host machine for almost 2 years, I have a lot of experience with network engineering, I am studying for the CCNA exam as well as I did a lot of labs on packet tracer and worked with real life physical network equipment, I have a physical home lab where I run my own DNS server (pi-hole), and I worked a lot with UFW, AWS NACLs and Security Groups, I am also experienced in ssh hardening and server administration, I can secure servers and restrict access only to authorised individuals. I also know VirtualBox very well, including VirtualBox network adapters, inter VM networking, additionally using pfSense as a virtual router to connect multiple VirtualBox networks. I also did some CTFs here and there, some tryhackme and hackthebox labs, used Kali to learn penetration testing basics from PortSwigger academy. And I know Docker very well, and how applications work, I have some experience with backend programming.&lt;/p&gt;

&lt;p&gt;So I am not a complete beginner to Cybersecurity. I have been doing a lot of things related to Cybersecurity. But day before yesterday is when I truly started getting deep into Cybersecurity. All my previous work involved some amount of Cybersecurity in it in some ways but this is the first time I started doing a 100% Cybersecurity only project. My college required students in the 7th semester to form a team and submit a project, a research-oriented one instead of an application-oriented one (like the previous semester's Capstone project). My friends Savitha, Srimathi and I formed a team and decided to do SIEM assistant, a chatbot to interface with an existing SIEM solution so that SOC analysts can talk to a chatbot in human language to make it write complex queries and the SOC analyst can review it and execute them right in the chat interface and see the filtered output. This would help save a lot of time for SOC analysts so that they don't have to spend a lot of time writing complex queries in a query language and debugging those queries and can instead focus on investigating the threats. &lt;/p&gt;

&lt;p&gt;But to do this I first have to understand Wazuh very well so I decided to do a Cybersecurity home lab in Wazuh, and I started it around day before yesterday evening. This is the first pure Cybersecurity resume worthy standalone project I ever started. I learnt SIEM basics from the internet and got a grip of what Wazuh is all about and decided to do a basic lab fully in Virtualbox with a Wazuh server and some log sources and do some attacks with Kali and generate some logs and analyse them and document it. This blog post is not the technical write up or the build log of that lab but my experience getting into Cybersecurity. For the lab required for the SIEM project, it will be hosted fully in AWS using Terraform because we are showcasing the assistant part in our project so we need a one click deploy for the lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the Network Architecture
&lt;/h2&gt;

&lt;p&gt;I initially planned on having different types of log sources. A Windows desktop log source, debian desktop log source, a pfSense firewall log source additionally with IDS/IPS, and a webserver that hosts a  as a vulnerable web application as a log source, and DNS server log source. And planned to launch recons and attacks on the network and the webserver and investigate all the logs from Wazuh. &lt;/p&gt;

&lt;p&gt;Here is what the initial plan looked like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4y9z0ajf5db4dcmpsxb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4y9z0ajf5db4dcmpsxb4.png" alt="network-architecture-1" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I put all the log sources in an internal network, and used a pfSense VM to connect it to the public internet. I chose internal because I wanted it to be isolated from the host, and I didn't choose NAT Network because I wanted control over the router instead of letting VBox manage it for me, since that is a log source.&lt;/p&gt;

&lt;p&gt;And I didn't want to put Kali attacker in the same internal network as I didn't want to log Kali's outbound internet access in the corporate SIEM lab for obvious reasons. So I put the Kali VM in another host only ethernet adapter network (here it doesn't matter if it is host only or internal), and connected it to the public internet via another pfSense VM.&lt;/p&gt;

&lt;p&gt;And I attached another network adapter to both pfSense VMs and put it in a separate internal network for the point to point link because I will have to set static routing between them so I need predictable stable IP addresses, which is why I didn't set up bridged adapter on the public link for those pfSense VMs to talk directly because if I did those will be assigned via DHCP from my real world router. So for public internet access I set NAT on the WAN interfaces of both pfSense as it didn't really matter at this point, since both pfSense has one for its own LANs and one for the point to point link called OPT1.&lt;/p&gt;

&lt;p&gt;I have a lot of VM base images from which I just clone if I need to do a lab with multiple VMs. The base images are pfSense, debian desktops and servers and etc. Every VM in the diagram except for Kali, pfSense and Windows were debian desktops and servers. After cloning all that, I set up the required network adapters on each device and assigned them the right network. Now all that was remaining is the host configuration. &lt;/p&gt;

&lt;h3&gt;
  
  
  pfSense behaves differently with non LAN adapters
&lt;/h3&gt;

&lt;p&gt;So I booted up the pfSense VMs first and set up the interfaces which was easy enough and set the LAN and WAN adapters appropriately along with DHCP for the LANs, and set static IPs on both the OPT1. Now I tried to ping one pfSense from the other using the IP set in another one's OPT1 but it just didn't work for some reason. I searched about it and only LAN is allowed to do whatever it wants, for OPT1 a separate firewall allow rule has to be set for communication to succeed. Coming from Cisco IOS routers, this was the first obstacle in this whole lab. So I set up the firewall rule and now I was able to ping both pfSense from each other. &lt;/p&gt;

&lt;p&gt;So I went ahead and booted up the servers and setup static IP, then I booted up the desktop VMs and checked for connectivity. Then I added static routes to both pfSense and then tried pinging the machines in the LAN from the Kali. It worked fine. By this time it was night so I decided to do the remaining part of the lab, which I thought would be just installing Wazuh server and setting up agents and log sources and doing some attacks and analysing the logs from the Wazuh server, the next day which would be yesterday and I went to sleep thinking it would be over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Next Day
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Wazuh Installation and Re-installation on Debian server VM
&lt;/h3&gt;

&lt;p&gt;So I booted up the all the VMs and tried to install Wazuh on the medium sized debian server I dedicated for Wazuh. I also made pi-hole resolve &lt;code&gt;wazuh-server.internal&lt;/code&gt; to the Wazuh server's IP beforehand. I opened the Wazuh docs quickstart page, copied the command which would download and run the install script, and executed it on the Wazuh server. It said OS not supported and only some Ubuntu, RHEL and Amazon Linux were supported. It told me to pass a flag with the install script to ignore that, and since I thought the debian VM can handle it I did that. So it began the installation and I had to wait for a lot of time as the downloading and installing was slow. After around 80% was done, it aborted the process and removed all the installed components and said the server is not big enough to handle the Wazuh and self deleted itself. So I allocated more resources to that server so that it can handle Wazuh and restarted the installation process. &lt;/p&gt;

&lt;p&gt;It said a previous installation exists but the previous time the script clearly said it was removed. And the script said to pass another flag to overwrite the existing installation and I did that but it couldn't overwrite it fully as certain processes and files weren't fully removed earlier and several ports that would be used by the Wazuh server were still being listened on by detached processes. There was no standardized way to completely remove every left over thing. So I decided to purge the entire Wazuh server VM and mind you every install and uninstall commands took a lot of time. So I deleted the entire VM and decided to setup a fresh Ubuntu server that is big enough to handle Wazuh.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ubuntu static IP issue
&lt;/h3&gt;

&lt;p&gt;So I downloaded the Ubuntu noble ISO which took a lot of time, I waited for it. And after it finished downloading, I installed a base image from which I planned to clone another VM for the Wazuh server so that I don't have to install Ubuntu whenever I need a server VM. The installation also took a lot more time, so I had to wait. By the time the installation was over it was afternoon after my lunch time. &lt;/p&gt;

&lt;p&gt;After finishing my lunch I got back to work and tried to set up static IP for the Wazuh server. For debian servers I just had to edit &lt;code&gt;/etc/network/interfaces&lt;/code&gt; and put something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iface eth0 inet static
    address 192.168.1.50
    netmask 255.255.255.0
    gateway 192.168.1.1
    dns-nameservers 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then reload the service called &lt;code&gt;networking&lt;/code&gt; and boom it works out of the box. But for Ubuntu, I have to edit a complicated yaml file and the syntax is too annoying even one extra indentation would mess up the whole thing but in debian indentation was not an issue. So I copy pasted the yaml values after giving the IP addresses to chatpgpt and making it generate the YAML file and ran &lt;code&gt;sudo netplan apply&lt;/code&gt; and it finally worked. &lt;/p&gt;

&lt;h3&gt;
  
  
  Ubuntu not seeing the whole disk
&lt;/h3&gt;

&lt;p&gt;I tried to install Wazuh server on the bigger Ubuntu VM, and mind you I did allocate enough resources to it so that I can install Wazuh on it and I did tell it to take up the full disk when installing the Ubuntu ISO and I never selected the LVM option. So when I downloaded and executed the install script I had to wait for a lot of time again. And it did the exact same thing it like earlier it finished around 80% of its job and then said the system is not big enough to handle Wazuh and self deleted all the components it installed. What?&lt;/p&gt;

&lt;p&gt;So I checked the disk space on the server and it turns out only a small portion of the entire disk is visible to the host OS because by default Ubuntu server wouldn't install it over the entire disk and would just set up a LVM that I have to manually extend to the whole disk. So I did that and restarted the install script. Which again complained about existing installations that I had to pass the overwrite flag again and it took a lot of time to download the packages and when it is supposed to install them it couldn't overwrite again due to left over files and services and detached processes listening on ports. It was not possible to investigate all of that and remove one by one as the scope of this lab is log analysis so I decided to abandon this current installation and restored a snapshot, thankfully I had a snapshot even if I didn't I would have cloned from that Ubuntu base server VM (which I still have to modify to make it take up the entire disk).&lt;/p&gt;

&lt;p&gt;And the first thing I did was extend the LVM to take up the whole disk. After that since it was fresh, I downloaded and executed the install script again. Which took a lot of time so I waited. And after a lot of time the server was installed and the username and password were printed on the terminal, so I opened up &lt;code&gt;wazuh-server.internal&lt;/code&gt; in the browser from the debian administrative desktop (as it is an internal network) and it worked when I logged in. So finally the server was installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accidental VirtualBox basic mode
&lt;/h3&gt;

&lt;p&gt;Approximately around this time I accidentally clicked on Basic mode in the per VM settings in VirtualBox and that changed the entire VirtualBox manager to basic mode so the clone VM option wasn't there and there was only one network adapter option on each VM and it only had 2 options NAT and Bridged. I was so confused and crashing out trying to find the clone button for 5 entire minutes and I stumbled on the basic/expert setting in per VM setting again and set it to expert and got all my buttons back, I was finally relieved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring pfSense+Suricata IDS/IPS as a log source
&lt;/h3&gt;

&lt;p&gt;I wanted network monitoring and IDS deployed on the corporate network and I wanted the IDS logs on the Wazuh. So I opened pfSense dashboard, added the Suricata package and set it up to send eve-json logs to the system. Then I set up remote logging in pfSense and made it send logs to Wazuh's IP on port :514. And then on wazuh server I installed rsyslog and made it save whatever comes on :514 to a separate file &lt;code&gt;pfSense.log&lt;/code&gt; and edited Wazuh's xml conf to monitor that log file as well.&lt;/p&gt;

&lt;p&gt;Now how does Wazuh server know it is pfSense that is sending the logs and no logs are being edited by unauthorized sources before it reaches the server? I don't know. I decided it is not my problem.&lt;/p&gt;

&lt;p&gt;And after Suricata and pfSense logging to remote server to was set up I refreshed Wazuh and pfSense did send some logs to the server and it was visible under the agent id &lt;code&gt;000&lt;/code&gt;. So whatever comes to :514 it gets read as agent &lt;code&gt;000&lt;/code&gt;'s activity. So no matter how many network devices are set up to remote syslog, it is all under agent id &lt;code&gt;000&lt;/code&gt;.  How do we group the logs based on the source and type and classify them? I have decided it is not my problem and in a real organization I would escalate this issue to the senior security analyst as the scope of this lab is to just do some log analysis on Wazuh not configure multiple log sources and aggregate them with precision.&lt;/p&gt;

&lt;p&gt;So I went ahead and did a nmap aggressive scan on the entire corp subnet expecting Suricata to log something related to it and after the scan was over I tried to refresh Wazuh dashboard and found out nothing. Why is Suricata not sending nmap scan incident logs to Wazuh server? I don't know. Probably because I haven't configured the exact rules to detect it. I have decided that I don't care to since IDS/IPS configuration is out of the scope of this lab and decided to drop Suricata entirely so that I can spend time on the actual scope of the lab.&lt;/p&gt;

&lt;p&gt;I opened the pfSense dashboard and removed the entire Suricata. Now just the regular firewall logs come to Wazuh under agent &lt;code&gt;000&lt;/code&gt;. Which was fine for now and I moved on to installing agents on the remaining hosts.&lt;/p&gt;

&lt;h3&gt;
  
  
  My PC hanged
&lt;/h3&gt;

&lt;p&gt;I use Linux by the way on my bare metal host machine as my daily driver that is why it was able to support all these VMs so far. Right before I started setting up agents on the hosts my PC just hanged. It was because 8 separate VMs, one windows and one bulky Ubuntu and one Kali, along with Brave browser, obsidian, and vscodium, 3 separate chromium processes were running. I just left my computer and took a break. When I came back it was locked but it was working, so I unlocked it and then stopped Brave, Obsidian and VSCodium. Then I shutdown and purged the webserver as nothing useful was on it. And I shut down the Kali VM and its pfSense. Some RAM was freed. Then the PC was working fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  pfSense DNS resolve host override subdomain field confusion
&lt;/h3&gt;

&lt;p&gt;I also decided to purge the pi-hole VM and make pfSense resolve domain names without an external server. So I edited the DHCP settings to make pfSense as the DNS server. And then in the host overrides, to add a manually entry to resolve &lt;code&gt;wazuh-server.internal&lt;/code&gt; to the Wazuh server's IP address I opened up that setting page in pfSense dashboard and there were 3 options&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;host name&lt;/li&gt;
&lt;li&gt;Domain&lt;/li&gt;
&lt;li&gt;IP&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In pi-hole GUI there are only 2 fields, FQDN and IP so I thought the first field host name is about the OS host name of the target server and entered the values like&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;wazuh-server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wazuh-server.internal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;10.0.0.4&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then &lt;code&gt;nslookup wazuh-server.internal&lt;/code&gt; failed but other public domains were working fine (after the DHCP settings have been reloaded in VMs that use DHCP and in the hosts that have static IP I manually edited it to use pfSense as its DNS server). I found out through the hard way the first field host name is a sub domain field. So currently &lt;code&gt;wazuh-server.wazuh-server.internal&lt;/code&gt; is being resolved to &lt;code&gt;10.0.0.4&lt;/code&gt; and &lt;code&gt;wazuh-server.internal&lt;/code&gt; is not even being cared about. &lt;/p&gt;

&lt;p&gt;This was really annoying because why would it make me enter the subdomain in a separate field if I am gonna have to make a new entry for another subdomain for the same top level domain? I figured out I was supposed to leave the first field empty if I wanted to resolve the main domain and I did that and finally internal DNS was back to normal. &lt;/p&gt;

&lt;h3&gt;
  
  
  Reducing the scope of this project
&lt;/h3&gt;

&lt;p&gt;Since the DNS server and web server are gone, we will only be collecting logs from Debian desktop, Windows Desktop, and the firewall. So the new network architecture looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6lkusp2cpdar0dcwkjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6lkusp2cpdar0dcwkjs.png" alt="network-architecture-2" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2 pfSense VMs have 1 GB RAM each, and Wazuh server and Windows have 8 GB RAM each,  Debian Desktop has 2 GB RAM and Kali has 8 GB RAM. So in total it is 32 GB RAM if all these VMs operated at maximum capacity but fortunately they won't and I am also going to reduce the RAM of Kali and other things when I do the attacks and ssh brute force on the hosts. And I have to run OBS to record the attacks and logs generation and inspection so it is a big problem for future me. &lt;/p&gt;

&lt;p&gt;Originally I planned to do webserver log monitoring and DNS server log monitoring so I can host a vulnerable web application on the webserver and attack it with Kali so I can have those logs in Wazuh too and also I planned to do DNS poisoning and capture logs from pi hole so another investigation can be done. Now due to my host's limitations I will only be doing File Integrity and Registry edit monitoring in windows and default monitoring in debian desktop and the only thing I will be doing with Kali is ssh brute force. &lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the agents
&lt;/h3&gt;

&lt;p&gt;Deploying the agents on the hosts was the easy part, I literally had to enter some values in the Wazuh dashboard and I got the commands to copy paste on the host and thankfully it worked out of box in the debian desktop. I tried installing some packages and removing them and it was shown correctly in the Wazuh server and the login and logout details. Thankfully this part worked out of the box.&lt;/p&gt;

&lt;p&gt;And I deployed an agent in Windows and did some registry edits and ran notepad as admin and edited some files, including the host files and hoped that it would show up in the Wazuh dashboard but nothing showed up. Turns out I have to install a separate thing on Windows for it to actually send all these data.&lt;/p&gt;

&lt;p&gt;By this time it was night so I decided to do rest the next day and just went to sleep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is the moment that I realised, this is Cybersecurity. Yes, this. All this. I am a junior and I am having to deal with all of these in my first cybersecurity lab, so I am pretty sure professionals have it 1000 times worse. I have decided this is worth it. I am getting into Cybersecurity. &lt;/p&gt;

&lt;p&gt;I also spent a lot of time documenting all these from my memory and made sure I didn't miss anything that happened yesterday because a lot of people that get into Cybersecurity and become a professional don't share their technical struggles. Maybe some on YouTube but I've only heard people say it is hard, I haven't seen anyone show the "why" it is hard publicly like this. I wanted to document it and share so that I can look back at this from the future and think "things were so easy back in the day", and recruiters if they stumble on this, they would know I am a guy that actually does things so I would be hire-able, and also people that are starting fresh could see this and other posts I will make in the future so they can see the reality of getting into Cybersecurity, the technical details part.&lt;/p&gt;

&lt;p&gt;I am gonna go ahead and configure the agents in debian and Windows after posting this and do the brute force and log analysis and document that part too. The deadline for this lab is Monday so I do have a lot of time to do that. The deadline exists because we are supposed to show weekly progress report to our college.&lt;/p&gt;

&lt;p&gt;Thanks for reading so far and goodbye.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>security</category>
      <category>homelab</category>
      <category>wazuh</category>
    </item>
    <item>
      <title>I recently learnt how to setup a NAT instance instead of NAT Gateway in AWS</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Thu, 25 Jun 2026 10:15:55 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/i-recently-learnt-how-to-setup-a-nat-instance-instead-of-nat-gateway-in-aws-5ec6</link>
      <guid>https://dev.to/shobanchiddarth/i-recently-learnt-how-to-setup-a-nat-instance-instead-of-nat-gateway-in-aws-5ec6</guid>
      <description>&lt;p&gt;GitHub repo: &lt;a href="https://github.com/ShobanChiddarth/nat-instance-aws-terraform" rel="noopener noreferrer"&gt;ShobanChiddarth/nat-instance-aws-terraform&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AWS NAT Gateway is a solution for letting EC2 instances in private subnets access the internet. Here is how it usually works. Private subnets would have a route table associated that has a default route &lt;code&gt;0.0.0.0/0&lt;/code&gt; pointed to a NAT gateway that is present in a public subnet (one that has a default route &lt;code&gt;0.0.0.0/0&lt;/code&gt; pointed to the internet gateway of the VPC).&lt;/p&gt;

&lt;p&gt;This way private instances can download and install software updates, pull public Docker images and so on without needing a public IP address. And also the internet would not have a way to access the private EC2 instances. This is similar to setting up Port Address Translation (NAT, masquerading) in on premises networks so that PCs can use the internet without needing a public IP address. This setup is extremely common in home networks.&lt;/p&gt;

&lt;p&gt;In this project I am basically replacing the NAT Gateway with another EC2 instance to save costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77perr42hru3df4fn68w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77perr42hru3df4fn68w.png" alt="architecture-diagram" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this demo project we have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 VPC&lt;/li&gt;
&lt;li&gt;2 subnets, one public and one private&lt;/li&gt;
&lt;li&gt;1 bastion instance in the public subnet&lt;/li&gt;
&lt;li&gt;1 NAT instance in the public subnet&lt;/li&gt;
&lt;li&gt;2 Private EC2 instances in the private subnet&lt;/li&gt;
&lt;li&gt;Route tables, security groups, IGW and all other miscellaneous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intended traffic flow is, the private EC2 instances will use the NAT instance as their router and the private subnet will have a default route pointing to the NAT instance's ENI. And the NAT instance will do masquerading and communicate with the internet.&lt;/p&gt;

&lt;p&gt;The bastion will be used to SSH into the private EC2 instances in order to check for internet connectivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  NAT Instance config
&lt;/h2&gt;

&lt;p&gt;You can create a regular EC2 instance but make sure to have these configured if you want it to be a NAT instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;source_dest_check = false&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;AWS, by default, blocks traffic that enters or leaves an EC2 instance if the traffic that enters or leaves the instance does not originate from it or is not destined for it. This to prevent EC2 instances from acting like routers. So you must disable this option on the NAT instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Security Group config
&lt;/h3&gt;

&lt;p&gt;The NAT instance security group must allow the traffic that will be forwarded from private subnets. For simplicity in this demo, all traffic from the VPC CIDR is allowed. And allow all egress traffic (default), and optional bastion config for SSH access. Here is how my NAT instance's SSH group works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"nat_instance_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"nat_instance_sg"&lt;/span&gt;
    &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NatInstanceDemoVPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"allow everything"&lt;/span&gt;

    &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
        &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
        &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
        &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bastion_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
        &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NatInstanceDemoVPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
        &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Route table (of private subnets)
&lt;/h3&gt;

&lt;p&gt;The route table must have a default route that directs all queries to the NAT instance's primary ENI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"to_nat_instance"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NatInstanceDemoVPC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

    &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
        &lt;span class="nx"&gt;network_interface_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nat_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary_network_interface_id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;aws_eip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nat_instance_eip&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. &lt;code&gt;user_data&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the most important part of that NAT instance config. The following is the script intended to be put in &lt;code&gt;user_data&lt;/code&gt; of the NAT instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
apt-get update
apt-get upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive

&lt;span class="nb"&gt;echo &lt;/span&gt;iptables-persistent iptables-persistent/autosave_v4 boolean &lt;span class="nb"&gt;true&lt;/span&gt; | debconf-set-selections
&lt;span class="nb"&gt;echo &lt;/span&gt;iptables-persistent iptables-persistent/autosave_v6 boolean &lt;span class="nb"&gt;true&lt;/span&gt; | debconf-set-selections

apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; iptables-persistent

sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; net.ipv4.ip_forward&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"net.ipv4.ip_forward=1"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/sysctl.d/99-nat.conf

&lt;span class="nv"&gt;PRIMARY_IFACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ip route | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/default/ {print $5; exit}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-A&lt;/span&gt; POSTROUTING &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIMARY_IFACE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-j&lt;/span&gt; MASQUERADE
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIMARY_IFACE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; state &lt;span class="nt"&gt;--state&lt;/span&gt; RELATED,ESTABLISHED &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PRIMARY_IFACE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT

netfilter-persistent save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I got the script from the internet and modified it a little. The script enables IP forwarding and configures iptables masquerading. I would say understanding each command is recommended before using it in production, but for a lab environment the script can be used as-is. However, on modern Linux distributions, nftables may be preferred over legacy iptables configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Make sure the NAT instance has an Elastic IP
&lt;/h3&gt;

&lt;p&gt;Assigning an Elastic IP ensures the NAT instance keeps the same public-facing address across stop/start cycles. The NAT functionality itself does not depend on a fixed public IP, but using an EIP avoids unexpected outbound IP changes and makes firewall allowlists, logging, and operational management easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/m9ZTLULQyw4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This video is a live demo of test infrastructure created for NAT instance test working and the private EC2 instances are able to access the internet through the public IP of the NAT instance.&lt;/p&gt;

&lt;p&gt;Instructions to run it are in the README of the repo linked above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Calculation
&lt;/h2&gt;

&lt;p&gt;I used &lt;a href="https://calculator.aws" rel="noopener noreferrer"&gt;calculator.aws&lt;/a&gt; for these numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  NAT Gateway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;$0.045/hour for just existing&lt;/li&gt;
&lt;li&gt;$0.045/GB for data processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in a month, it would be &lt;code&gt;0.045*24*30 = $32/month&lt;/code&gt; + 0.045 for every GB of data transfer. For illustration assume 300GB data transfer per month. Which means `300*0.045 = $13.5/month.&lt;/p&gt;

&lt;p&gt;So in total it would be way more than &lt;code&gt;32+13.5= $45.5&lt;/code&gt; so definitely more than $50 per month.&lt;/p&gt;

&lt;h3&gt;
  
  
  NAT Instance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Shared on-demand Linux &lt;code&gt;t3.micro&lt;/code&gt; in &lt;code&gt;ap-south-1&lt;/code&gt; =&amp;gt; $8.18/Month&lt;/li&gt;
&lt;li&gt;8GB gp3 with minimum values for input output (as it won't be modified that much) =&amp;gt; $0.73/month =&amp;gt; round it up to $1/month as updates and patching will take some IO&lt;/li&gt;
&lt;li&gt;EIP for a month = $3.65 USD/month&lt;/li&gt;
&lt;li&gt;300 GB of network egress per month would be covered in total network egress bill anyway, there is no per EC2 billing. When using NAT Gateway the data transfer will be billed twice, once when the NAT gateway processes it and once when it leaves AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So in total it will be &lt;code&gt;8.18+1+3.65 = $12.83/month&lt;/code&gt; which means around $15 per month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;NAT Gateway&lt;/th&gt;
&lt;th&gt;NAT Instance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;~$50/month&lt;/td&gt;
&lt;td&gt;~$15/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;~$600/year&lt;/td&gt;
&lt;td&gt;~$180/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You are saving $420/year ($35/month), not counting the enormous data processing of high stakes environments and counting it as &lt;code&gt;t3.micro&lt;/code&gt; which is what is available for free tier in &lt;code&gt;ap-south-1&lt;/code&gt; so in non-free tier accounts you could use even cheaper EC2 instance types (and different pricing options) given that you manage the NAT instances, patch them regularly, provision according to load and etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NAT Gateway is better
&lt;/h2&gt;

&lt;p&gt;NAT Gateway is a managed service that provides built-in availability within an Availability Zone, automatic scaling, and no patching requirements. A NAT instance is cheaper but introduces operational responsibility and can become a single point of failure if not designed for redundancy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I have now learnt a cost efficient way to allow EC2 instances in private subnets to access the internet and I will be using this in the upcoming projects (the one I am currently building is telegram proxy server infrastructure for an entire country).&lt;/p&gt;

&lt;p&gt;Earlier I ran into the cost problem of NAT Gateways for small college projects when I wanted to let private EC2 instances temporarily access the internet so &lt;a href="https://dev.to/shobanchiddarth/aws-terraform-infrastructure-for-a-fastapipostgresql-backend-3dip#nat-gateway-cost-problem"&gt;what I did earlier&lt;/a&gt; was I deployed the infra on terraform and immediately deleted the NAT gateway to avoid costs.&lt;/p&gt;

&lt;p&gt;Now I won't have to worry about that and I can just deploy another EC2 instance for cheaper rates that acts as a NAT gateway.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>cloudcomputing</category>
      <category>finops</category>
    </item>
    <item>
      <title>Telegram Ban in India has been lifted, for now</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Tue, 23 Jun 2026 10:34:30 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/telegram-ban-in-india-has-been-lifted-for-now-3g69</link>
      <guid>https://dev.to/shobanchiddarth/telegram-ban-in-india-has-been-lifted-for-now-3g69</guid>
      <description>&lt;p&gt;It is June 23, 15:38 as I am writing this and the Telegram ban on India has been lifted, temporarily. It was supposed to be lifted at today morning 00:00 but it wasn't and I had to use &lt;a href="https://github.com/ShobanChiddarth/telegram-proxy-server-aws" rel="noopener noreferrer"&gt;my proxy&lt;/a&gt; to access Telegram until around afternoon. Some of my friends were able to use Telegram without proxy or VPN and some couldn't access Telegram without proxy or VPN (all of them are in India).&lt;/p&gt;

&lt;p&gt;I say it has been temporarily lifted because there is a NEET exam coming next year so it most probably will be banned again and also governments cannot be trusted, especially Indian government due to the reasons I covered in the previous blog posts of this series. So if India wants to censor the civilians from the internet to restrict the flow of information for whatever reason like they did in Russia and Iraq and even China, India will block access to all Social Media and Telegram for sure.&lt;/p&gt;

&lt;p&gt;In which case the public should be prepared to oppose a tyrannical regime's censorship and have ways to freely access the internet and exercise your god given right to freedom of speech in order to expose a corrupt government that exploits its citizens.&lt;/p&gt;

&lt;p&gt;I am doing my part by building an infrastructure for Telegram proxy servers that can scale and handle traffic for an entire country. Anyone with an AWS account will be able to get the terraform files from the GitHub repo and then launch a proxy instance in one &lt;code&gt;terraform apply&lt;/code&gt; command. If you want to see the development for that live, you can check out the &lt;code&gt;engineering-for-scale&lt;/code&gt; branch in the GitHub repo I shared above. In the &lt;code&gt;main&lt;/code&gt; branch, the terraform files are configured to launch just one EC2 as a MTProto proxy server. &lt;/p&gt;

&lt;p&gt;You still should not be relying on Telegram as your sole communication app. That being said, thanks for reading so far. In the upcoming revision of the Telegram proxy infrastructure that can handle load for an entire country, I have planned to implement Auto Scaling based on CPU, Network Load Balancer, Lambda CRUD app for manually overriding auto scaling instance counts, secrets mounting via EFS, etc. I will publish another post in detail after I finish it.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>freedom</category>
      <category>telegram</category>
    </item>
    <item>
      <title>Deploying a Telegram MTProto Proxy on AWS with Terraform</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Sat, 20 Jun 2026 17:46:28 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/deploying-a-telegram-mtproto-proxy-on-aws-with-terraform-25e5</link>
      <guid>https://dev.to/shobanchiddarth/deploying-a-telegram-mtproto-proxy-on-aws-with-terraform-25e5</guid>
      <description>&lt;p&gt;GitHub link: &lt;a href="https://github.com/ShobanChiddarth/telegram-proxy-server-aws" rel="noopener noreferrer"&gt;shobanchiddarth/telegram-proxy-server-aws&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/shobanchiddarth/how-to-circumvent-the-telegram-ban-in-india-36fl#2-use-a-telegram-proxy"&gt;previous blog post&lt;/a&gt; I talked about using proxy servers for Telegram to bypass censorship. I also said using someone else's proxy server makes you less anonymous as the proxy server admins can easily see your traffic meta data. That is why I created an AWS infrastructure to one-click-deploy your own Telegram MTProto proxy instance in AWS cloud. Here is how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6f9lmiy3t33bc7wa6vo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6f9lmiy3t33bc7wa6vo.png" alt="architecture-diagram" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When deploying the terraform code to AWS as per the instructions in the GitHub, a new VPC is created in Thailand with one subnet, and one EC2 is launched there. That one EC2 pulls the telegram mtproto proxy zero config container and runs it volume mapped and makes the secrets file accessible to the host. And then the host pulls another docker image of a FastAPI app I wrote (code in the GitHub repo) to serve the proxy connection details (including that secret) and runs it volume mapped and servers it over port 80 while the proxy server is running over port 443.&lt;/p&gt;

&lt;p&gt;The architecture has been kept simple because the intended usage is just one group of friends or family. The current configuration is enough to handle that much load. The EC2 instance type is &lt;code&gt;t3.micro&lt;/code&gt; which is more than enough for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Proxy Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MTProto
&lt;/h3&gt;

&lt;p&gt;MTProto is a custom protocol for proxy servers for Telegram. When you host a Telegram proxy server and enter the details of it in a Telegram client, the Telegram client sends all outbound requests to that server, encrypted and obfuscated, instead of to Telegram directly so that any regional restrictions can be bypassed and also it will look like a regular HTTPS website since it is made to run on port 443. ISP, government, and big tech will never know it is a Telegram proxy server as the connection is encrypted. &lt;/p&gt;

&lt;h3&gt;
  
  
  Region choice explanation
&lt;/h3&gt;

&lt;p&gt;In the GitHub repo of the IaC, region is set to Thailand. This is because it is the closest AWS region to India that is not in a place that could be affected by the recent war and also not China. So that Indian users who connect to it will have low latency, since Telegram is banned in India (currently) there is no point in hosting it in India. If you want to host it anywhere else the GitHub repo linked above has instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security implications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Port 22 open to internet
&lt;/h3&gt;

&lt;p&gt;Currently, the port 22 of the EC2 is exposed to the public internet. This is generally considered bad practice but it is a temporary measure and also I didn't want to commit my public IP address in a GitHub repo. When you host it, for more security, you can do any of the following&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the security group rules to allow only your public IP to port 22&lt;/li&gt;
&lt;li&gt;Use SSM&lt;/li&gt;
&lt;li&gt;Use AWS VPN (it is costly)&lt;/li&gt;
&lt;li&gt;Use EC2 Instance Connect from the AWS console (you will have to allow the port to AWS IP ranges)&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your use case, you can choose what is best for you to avoid exposing port 22 to the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anyone who has the public IP of the EC2 can connect to the Telegram proxy (Port 80 open to the internet)
&lt;/h3&gt;

&lt;p&gt;This is because both port 80 and 443 are open to the entire internet. In port 80, the management container used to expose proxy server connection details (which includes the secret) is running. I created it this way because the intended use for this project is the proxy server will be hosted by one person and will be shared to a small group of people who will keep it private. So if you want to hide the proxy server connection details from anyone who has the public IP then I suggest you don't allow port 80 to the entire internet, and instead allow it to only your public IP address or stop the container entirely and ssh into the machine using any of the above ways and read &lt;code&gt;/data/secret&lt;/code&gt; manually and store it and send it to other people manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current architecture can't handle enormous load
&lt;/h3&gt;

&lt;p&gt;The EC2 uses t3.micro (as it is in free tier) and there is only one available EC2 instance. And a lot of TCP segments will be sent back and forth per second per user. The current configuration can withstand a few users, like a few friends groups and family members, not entire towns or cities or countries. So don't share the connection details publicly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The instance runs 24/7
&lt;/h3&gt;

&lt;p&gt;If you don't want the instance to run 24/7 you can shut it down when you don't need it to save costs. Auto scaling groups and load balancing has not been set up since the target usage is a very small group of people. So the complexity is unnecessary. Elastic IP is used instead of associating a public IP address to an EC2 so that even after reboot, the EC2 still gets the same IP address. This way, the clients don't need to be reconfigured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Calculation
&lt;/h2&gt;

&lt;p&gt;We are going to use &lt;a href="https://calculator.aws" rel="noopener noreferrer"&gt;calculator.aws&lt;/a&gt; to calculate the monthly costs for the current setup. Currently, we will be billed for the following.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;EC2 (t3.micro) running 24/7 (actually less than 24/7, as it will be manually shut down sometimes)&lt;/li&gt;
&lt;li&gt;EIP for the EC2&lt;/li&gt;
&lt;li&gt;EBS for the EC2 (gp3 8GB)&lt;/li&gt;
&lt;li&gt;Around 5 GB of internet egress (ingress is free) per month&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I entered the details in the AWS cost calculator. Here are the results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F256yj7n8ixq1v03khk7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F256yj7n8ixq1v03khk7e.png" alt="aws-calculator-output" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The estimate is $162.84/year. Which is $13.57/month. In India it is ₹1,280.89/month and ₹15,370.71/year. You can check it out &lt;a href="https://calculator.aws/#/estimate?id=60c552a5c072140661db283521c3f35f00f58aeb" rel="noopener noreferrer"&gt;here&lt;/a&gt; (this link will expire in 1 year, here is the json export in case it does expire).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Telegram Proxy Server singular"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Total Cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"13.57"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"upfront"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"12 months"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"162.84"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en_US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Created On"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20/06/2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Legal Disclaimer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWS Pricing Calculator provides only an estimate of your AWS fees and doesn't include any taxes that might apply. Your actual fees depend on a variety of factors, including your actual usage of AWS services."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Share Url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://calculator.aws/#/estimate?id=ff8e02fad667c558874ab361764f3189427d0c64"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Services"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amazon EC2 "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Proxy Server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia Pacific (Thailand)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9.23"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"upfront"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"12 months"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"110.72"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Tenancy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shared Instances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Operating system"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Linux"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Workload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Consistent, Number of instances: 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Advance EC2 instance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Pricing strategy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"On-Demand Utilization: 24 Hours/Day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Enable monitoring"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"disabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"DT Inbound: Internet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5 GB per month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"DT Outbound: Internet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5 GB per month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"DT Intra-Region:"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 TB per month"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Amazon Elastic Block Store (EBS)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default attached to EC2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia Pacific (Thailand)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.69"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"upfront"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"12 months"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8.28"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Number of volumes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Average duration of volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"730 hours per month"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Storage amount per volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8 GB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Snapshot Frequency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No snapshot storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Provisioning IOPS per volume (gp3)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"General Purpose SSD (gp3) - Throughput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"125 MBps"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Public IPv4 Address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TProxyVPC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia Pacific (Thailand)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Service Cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3.65"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"upfront"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"12 months"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"43.80"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"Properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"Number of In-use public IPv4 addresses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This cost estimate is not for free tier accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step process to connect to the proxy server you deployed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Steps to deploy
&lt;/h3&gt;

&lt;p&gt;Check the README of the GitHub repo I linked in the top of this blog.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to connect to the proxy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Visit the public EIP of your EC2 instance in a browser (as the container runs in port 80).&lt;/li&gt;
&lt;li&gt;The connection details for MTProto client will be visible.&lt;/li&gt;
&lt;li&gt;Follow &lt;a href="https://core.telegram.org/proxy#adding-a-proxy" rel="noopener noreferrer"&gt;these steps&lt;/a&gt; to enter those details into a Telegram client&lt;/li&gt;
&lt;li&gt;You will be able to use Telegram over a proxy&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What I normally do for the above mentioned security implications
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I deploy it&lt;/li&gt;
&lt;li&gt;I open the public IP in a browser&lt;/li&gt;
&lt;li&gt;I note down the connection details&lt;/li&gt;
&lt;li&gt;I then remove the &lt;code&gt;allow 22&lt;/code&gt; and &lt;code&gt;allow 80&lt;/code&gt; rules from the security groups. By commenting this part in terraform and doing &lt;code&gt;terraform apply&lt;/code&gt; again.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I successfully created an AWS Terraform project for one click deploying a singular Telegram MTProto proxy server. I did this out of necessity, as government is banning Telegram currently in my area. Telegram is a private messaging app not owned by the government or Mark Zuckerberg and that is why they are trying as much as they can to stop people from using it, I already covered this in my previous blog post.&lt;/p&gt;

&lt;p&gt;Now with this repo, anyone in the world can deploy a proxy server for Telegram easily and help their friends who are being censored from the internet. Right now this cannot handle massive load as I have mentioned above but I plan to improve this architecture in the near future by using auto scaling groups and AWS Network Load Balancer (load balance per TCP connection) between the proxy servers, bastion based SSH management, and lambda based manual scheduling and starting and stopping of servers, and the NLB DNS name will be used as the hostname in Telegram clients. But this is enough for a few number of people.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>cloud</category>
      <category>security</category>
    </item>
    <item>
      <title>How to circumvent the Telegram ban in India</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:17:18 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/how-to-circumvent-the-telegram-ban-in-india-36fl</link>
      <guid>https://dev.to/shobanchiddarth/how-to-circumvent-the-telegram-ban-in-india-36fl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The Indian government has banned Telegram, a secure and private messaging app not owned by Mark Zuckerberg nor controlled by the Indian government for a week, from June 16 to June 22. The reasons they stated are someone used Telegram to leak the NEET-UG exam papers.&lt;/p&gt;

&lt;p&gt;Source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bbc.com/news/articles/cn074j04l3eo" rel="noopener noreferrer"&gt;https://www.bbc.com/news/articles/cn074j04l3eo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The leaks have moved over to other apps and the 150 million (15 crores) civilians who use Telegram as a messaging service so that they can have privacy from the spying eyes of Meta and government are punished instead. &lt;/p&gt;

&lt;p&gt;This is what the &lt;a href="https://internetfreedom.in" rel="noopener noreferrer"&gt;IFF (Internet Freedom Foundation)&lt;/a&gt; had to say regarding the ban:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://x.com/internetfreedom/status/2066774102610763985" rel="noopener noreferrer"&gt;https://x.com/internetfreedom/status/2066774102610763985&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://x.com/internetfreedom/status/2066864224966738196" rel="noopener noreferrer"&gt;https://x.com/internetfreedom/status/2066864224966738196&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case you are not able to view the tweets I have copy pasted them below&lt;/p&gt;

&lt;h3&gt;
  
  
  IFF Tweet: Statement : Shutting down Telegram is a band aid solution and is a disproportionate answer to exam fraud
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Internet Freedom Foundation objects to the directions announced today in the National Testing Agency's press release on action against the Telegram platform. On the NTA's istry of Electronics and Information Technology has, under Section 69A of the Information Technology Act, 2000, restricted access to the whole of Telegram in India until 22 June 2026, and has separately ordered the platform to switch off message-editing for every Indian user until 30 June 2026. This is a blunt, nationwide measure aimed at the conduct of rampant fraud rackets, and on the Government's own admission is constitutionally incompatible.

At the outset it is important to note that Section 69A and the Blocking Rules of 2009 framed under it allow the Government to block access to specific “information” on a computer resource. They do not extend to switching off an entire intermediary, still less to ordering a company to redesign its product by removing a feature for a whole country. In Shreya Singhal v Union of India, the Supreme Court upheld Section 69A because it is narrow and hedged with procedural safeguards. Reading it to authorise shutting down a platform that lakhs use is an overbroad restriction by the NTAs own admission. For the message-editing direction the release identifies no source of power at all. If one exists, the order must say so.

The release argues against itself

A restriction on access has to be the least intrusive measure that achieves its aim as per the constitutional test of proportionality laid down in Justice K.S. Puttaswamy v. Union of India (2017) and applied in Anuradha Bhasin v. Union of India (2020). The NTA's own narration shows the block fails its nodal agency, the release says, “has secured the prompt take-down of a substantial number of Telegram channels, groups and bots”, and this targeted work “is the reason the harm caused by these rackets has been contained to the extent it has”. If channel level takedown contained the harm, the case for a blanket block collapses and hence the Government has reached for a heavier tool while conceding that a lighter one was working. The collateral cost sits on the record too as noted in the press release. The block, the NTA accepts, “affects lakhs of citizens who use the Telegram platform for legitimate personal, educational, professional and informational purposes”. The release also says there is "no such paper available outside the secured examination chain" and that “the security of the examination is unaffected by the action taken”. If the exam is secure and no leak exists, what is being suppressed is rumour, and rumour cannot justify closing a platform when specific blocking and criminal prosecution remain available.

Students use of Telegram

The block of telegram is reactive and ineffective and will punish ordinary users instead of addressing the systemic source of exam leaks. This blocking comes in the final days of NEET preparation, when thousands of students depend on Telegram for study groups, doubt-clearing, and shared resources. Also, it is important to consider that the source of exam papers leak will occur from inside the system, among insiders and across the printing and logistics chain, with the platform being the most downstream channel for distribution. Hence, switching off Telegram, is merely a deflection from the repeated failures that will continue while media attention is directed towards this Telegram ban.

Lack of transparency

At present only a press release from the NTA has been provided, which recommended the block but the reasoned order of MeitY, the authority that issued it, has not been released. The Anuradha Bhasin decision requires that orders restricting access be published so they can be tested in court. Here the order, and the reasoning of the committee behind it, stay out of view, and we do not know whether Telegram was heard at all. An announcement of a block is no substitute for an order the affected party can challenge.
Blunt to enforce and very easy to evade
Usually, app-level blocks run through IS-level DNS and IP filtering. They are over inclusive, sweeping in lawful use, yet simple to evade as a determined exam leak racket moves to a VPN or a mirror within minutes while ordinary users lose the service for a week.

We ask the Government to:

1) Publish the MeitY Section 69A order and the NTA recommendation behind it, with reasons;
2) State the legal basis for the message editing direction, or withdraw it;
3) Confirm whether Telegram was given a hearing under the Blocking Rules, and place the committee's record before any court that hears a challenge; and
4) Lift the platform-wide restriction and rely on the targeted takedowns the NTA itself credits with containing the harm.

We emphasise that the NEET (UG) 2026 re-examination is worth protecting and it concerns the future of lakhs of aspirants. It requires securing the entire process of examination rather than reaching for purported band aid solutions that instead cause more harm. The State cannot switch off a service used by lakhs to answer the wrongdoing of a few, and cannot do it through an order no one affected is allowed to read. On its own facts, the Government has done both.

New Delhi, 16 June 2026.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Images attached&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyof8h3qv1a53qqm87bu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyof8h3qv1a53qqm87bu.png" alt="IFF-Tweet-1-img-1" width="539" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkms6vrijvzekiz1y439x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkms6vrijvzekiz1y439x.png" alt="IFF-Tweet-1-img-2" width="494" height="795"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd5gsz77qwfyp98cb9ol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd5gsz77qwfyp98cb9ol.png" alt="IFF-Tweet-1-img-3" width="533" height="777"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IFF Video: Banning Telegram won't fix a broken examination system.
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2066864224966738196-656" src="https://platform.twitter.com/embed/Tweet.html?id=2066864224966738196"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2066864224966738196-656');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2066864224966738196&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  How to circumvent the ban
&lt;/h2&gt;

&lt;p&gt;The government argues they are shutting down Telegram to stop the spread of leaked exam papers and the people involved in the illegal stuff have already found ways to circumvent it and move to other apps, so why shouldn't you, a law abiding citizen not have as much freedom as the criminals? Assuming you are a regular law abiding civilian who is dependent on Telegram for communicating with other people and you don't want the government or Meta to look at your private messages, here are the ways you can circumvent the ban.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use a VPN
&lt;/h3&gt;

&lt;p&gt;Any VPN app that lets you connect to a VPN server in a different country will do. I recommend Proton VPN because you don't even have to sign up for an account and still get unlimited data. I am using it right now to access Telegram, without a Proton VPN account, and it works. Download the Proton VPN app, and then connect the VPN, and open Telegram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://protonvpn.com/" rel="noopener noreferrer"&gt;https://protonvpn.com/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use a Telegram Proxy
&lt;/h3&gt;

&lt;p&gt;In case the government also banned VPNs or you are not able to use a VPN for any reason, you can use a proxy for Telegram. &lt;a href="https://core.telegram.org/proxy#adding-a-proxy" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is how to add a proxy.&lt;/p&gt;

&lt;p&gt;And here are a bunch of public proxy servers ready to use : &lt;a href="https://mtpro.xyz/mtproto" rel="noopener noreferrer"&gt;https://mtpro.xyz/mtproto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are more and you can find them easily on Google. Just note that using a proxy makes you less private as the proxy server admins can easily see your traffic meta data and etc, I will publish another blog on how to run your own Telegram proxy server. Until then, proton VPN and cloudflare WARP is free to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cloudflare WARP
&lt;/h3&gt;

&lt;p&gt;Even if you are in India, Cloudflare  WARP can circumvent Telegram ban. It is a VPN like app that routes your traffic through Cloudflare servers.&lt;/p&gt;

&lt;p&gt;Download it here: &lt;a href="https://one.one.one.one/" rel="noopener noreferrer"&gt;https://one.one.one.one/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is an action directly targeted to remove privacy and freedom of the Indian people step by step. &lt;/p&gt;

&lt;p&gt;This is not the first time Indian authorities have reached for a blunt instrument over a narrow problem. Access Now's #KeepItOn coalition has tracked 771 government-ordered internet shutdowns in India between 2016 and 2023, more than any other country in the world, for five consecutive years running. Jammu and Kashmir alone endured a 552-day blackout starting in August 2019, the longest internet shutdown ever recorded anywhere. The stated justification has shifted each time, protests, communal violence, exams, unrest, but the underlying pattern repeats: a blunt, platform-wide or region-wide shutdown standing in for narrower, targeted enforcement.&lt;/p&gt;

&lt;p&gt;It has also gone beyond blocking apps. Jammu and Kashmir has criminalized the circumvention tools themselves, banning unauthorized VPN use under Section 163 of the Bharatiya Nagarik Suraksha Sanhita. Around 800 people have already been penalized through phone searches for using banned applications.&lt;/p&gt;

&lt;p&gt;Telegram is being switched off this week over exam fraud. The justification will be different next time. The willingness to reach for this tool, and the tools themselves, have already been demonstrated repeatedly. People should understand how VPNs, proxies, and circumvention work before they need them, not after. The Indian government is your enemy and cannot be trusted. They previously planned on banning the entire proton suite because someone sent a fake bomb threat using a proton mail address (which I suspect is an inside job from the government to make up an excuse to ban proton in order to reduce people's freedom and privacy) and also any VPN server running in India is legally required to store logs and share them with the government, which is one of the reasons ProtonVPN moved away from India.&lt;/p&gt;

&lt;p&gt;Sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://restofworld.org/2024/india-internet-shutdown-record/" rel="noopener noreferrer"&gt;https://restofworld.org/2024/india-internet-shutdown-record/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.accessnow.org/press-release/keepiton-internet-shutdowns-2022-india/" rel="noopener noreferrer"&gt;https://www.accessnow.org/press-release/keepiton-internet-shutdowns-2022-india/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thewire.in/rights/india-asia-pacific-and-the-surge-of-internet-shutdowns-in-2025-10-key-takeaways" rel="noopener noreferrer"&gt;https://thewire.in/rights/india-asia-pacific-and-the-surge-of-internet-shutdowns-in-2025-10-key-takeaways&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://proton.me/blog/india-block-proton-mail" rel="noopener noreferrer"&gt;https://proton.me/blog/india-block-proton-mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thewire.in/government/it-ministry-decides-to-block-proton-mail-after-fake-bomb-threats-in-tamil-nadu-report" rel="noopener noreferrer"&gt;https://thewire.in/government/it-ministry-decides-to-block-proton-mail-after-fake-bomb-threats-in-tamil-nadu-report&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://protonvpn.com/blog/servers-india" rel="noopener noreferrer"&gt;https://protonvpn.com/blog/servers-india&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reliance also tried to block Telegram outside of India, more on that in the next blog.&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>freedom</category>
      <category>telegram</category>
    </item>
    <item>
      <title>On Age Verification</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Wed, 27 May 2026 02:09:33 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/on-age-verification-3bn2</link>
      <guid>https://dev.to/shobanchiddarth/on-age-verification-3bn2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you have been living under a rock (good for you), several states in America and the country of Brazil have introduced a new law that forces operating system developers to implement "age attestation" of the user, you will have to provide your age and then an age bracket will be stored which will be broadcast to every app and service you use in your operating system, and app stores (apt sources) are required to acknowledge this and filter software based on age, and assume lowest age bracket if the age is not given.&lt;/p&gt;

&lt;p&gt;Age verification tracker: &lt;a href="https://github.com/BryanLunduke/DoesItAgeVerify/" rel="noopener noreferrer"&gt;https://github.com/BryanLunduke/DoesItAgeVerify/&lt;/a&gt; (fork that maintains links to original developer statements: &lt;a href="https://github.com/softcookiepp/DoesItAgeVerify" rel="noopener noreferrer"&gt;https://github.com/softcookiepp/DoesItAgeVerify&lt;/a&gt;). This has all information regarding it.&lt;/p&gt;

&lt;p&gt;In this blog post, I will share my opinions on it and explain why it is impossible to enforce these laws as well as impossible to comply with these laws. It is actually illegal to follow the US law according to the US law, I will explain below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facebook
&lt;/h2&gt;

&lt;p&gt;Meta is behind this. Meta is pulling the strings and getting the law makers to pass such laws. Here is the proof:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20260313090844/https://www.reddit.com/r/linux/comments/1rshc1f/i_traced_2_billion_in_nonprofit_grants_and_45/" rel="noopener noreferrer"&gt;https://web.archive.org/web/20260313090844/https://www.reddit.com/r/linux/comments/1rshc1f/i_traced_2_billion_in_nonprofit_grants_and_45/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20260314074025/https://www.reddit.com/r/linux/comments/1rtd51g/update_i_pulled_irs_filings_for_the_org_that/" rel="noopener noreferrer"&gt;https://web.archive.org/web/20260314074025/https://www.reddit.com/r/linux/comments/1rtd51g/update_i_pulled_irs_filings_for_the_org_that/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4KyiLyOxux8" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=4KyiLyOxux8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since Mark Zuckerberg is the mastermind behind all these, and due to how &lt;a href="https://www.thestreet.com/technology/zuckerberg-old-remark-about-facebook-users" rel="noopener noreferrer"&gt;trustworthy&lt;/a&gt; he is&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4qt99cq22isybzb87n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4qt99cq22isybzb87n5.png" alt="tweet-screenshot-of-zuck-calling-people-dumbfucks" width="600" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;it is logical to expect the very next step from the lawmakers is to make you upload your government issued ID photo to the operating system before setting up the account. So that every action you take in the computer, every local text file you save, every message you send in end to end encrypted messaging apps, every local file you save, every email you send by gpg encrypting it, every social media post you make from an anonymous account, can be pinpointed to who exactly you are and your location, so that the government can keep a social credit score for people and arrest anyone that disagrees with the government and enforce a totalitarian regime like 1984 or present day China. &lt;/p&gt;

&lt;p&gt;This is their plan. It is a slippery slope. It is Facebook after all. Now they push for "age attestation" which is "just a minor thing" according to some people and after it is accepted as a "usual thing" they push for a little more like precise age numbers and then you end up with ID. &lt;/p&gt;

&lt;h2&gt;
  
  
  Linux
&lt;/h2&gt;

&lt;p&gt;The law keeps definitions of "Operating System", "User", "Account" as vague as possible to enforce it into as many places as possible. And anyone with a functioning mind would think the entire Linux community, built around privacy and security, would be vehemently opposed to this law and refuse to follow it and try as much as possible to get this law deleted. But that is where you are wrong. At least most of the people, the users, are against the law.&lt;/p&gt;

&lt;p&gt;Ubuntu will comply and Debian will help downstream distros implement it (it is in the tracker). And Arch Linux, an OS literally made to give you full control over your computer, says &lt;a href="https://www.youtube.com/watch?v=wR-zJKdAkOc&amp;amp;t=363s&amp;amp;pp=ygUsbHVuZHVrZSBhcmNoIGxpbnV4IG9wcG9zaW5nIGFnZSB2ZXJpZmljYXRpb24%3D" rel="noopener noreferrer"&gt;opposing age verification is violation of code of conduct&lt;/a&gt;. There is more on Lunduke's (the only journalist covering these kinds of tech news you wouldn't find on mainstream media) channel. And also &lt;code&gt;systemd&lt;/code&gt;, an init process that is supposed to start the system, has a module called &lt;code&gt;userdb&lt;/code&gt; and it previously stored a lot of PII like name and email and now it stores user birthdate to comply with the law so that xdg-desktop-portal can use it as a reference to implement the age attestation (&lt;a href="https://github.com/systemd/systemd/pull/40954?ref=itsfoss.com" rel="noopener noreferrer"&gt;PR link&lt;/a&gt;). And also &lt;a href="https://www.youtube.com/watch?v=M3erhbwqIAM&amp;amp;pp=ygUlbGludXggcmVkZGl0IGNlbnNvcnMgYWdlIHZlcmlmaWNhdGlvbg%3D%3D" rel="noopener noreferrer"&gt;linux reddit censors age verification related posts&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  systemd
&lt;/h3&gt;

&lt;p&gt;When I started out with Linux I did not understand the hate around systemd. I thought it was just a normal piece of software with unnecessary drama and also it worked kinda fine like it took care of things and was programmable so I didn't understand why people did not like it and moved away from it. But now I do. It is owned by RedHat and is extremely bloated and does things it is not required to do for no reason at all. I hated it more and more as I was going deeper into computers and Linux. It has a local DNS middleman server for god knows why the system needs another middleman when I run my own DNS server. I &lt;a href="https://www.linkedin.com/posts/shobanchiddarth_linux-mint-is-not-affected-by-systemds-userdb-activity-7445124215424335873-k4lf" rel="noopener noreferrer"&gt;had to configure a lot of things&lt;/a&gt; to make sure the &lt;code&gt;systemd-resolved&lt;/code&gt; stays shut down. An init process storing user PII, and modifying it to comply with privacy invasive freedom restricting laws, and resolving DNS, managing IP addresses of network interfaces (&lt;code&gt;systemd-networkd&lt;/code&gt;) is too much for an init process, it increases the attack surface and adds too much unnecessary bloat to the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distro hopping
&lt;/h3&gt;

&lt;p&gt;The only developers who oppose this law are listed in the tracker I shared above. I personally use Linux Mint and Mint is currently not affected by systemd storing user birthdate because Mint does not ship with &lt;code&gt;systemd-userdbd&lt;/code&gt; (&lt;a href="https://github.com/BryanLunduke/DoesItAgeVerify/issues/25" rel="noopener noreferrer"&gt;Source&lt;/a&gt;) so even if upstream software like xdg-desktop-portal implement it, it will have no point of reference for the user birthdate. However, that is only true for the current state of the OS (22.3 zena). The developers of Mint have not made any public statements regarding age verification and assuming the worst, if they implement age verification by shipping the next update with &lt;code&gt;systemd-userdbd&lt;/code&gt; or in some other way, I can simply refuse the dist upgrade. Also I am currently refusing all updates to systemd related packages using this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold &lt;span class="k"&gt;*&lt;/span&gt;systemd&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the dist upgrades have been refused and systemd related packages have been held back from updating, meaning I have a lot of time to switch to an operating system that publicly opposes age verification as well as does not have systemd. I am choosing to go with Artix (Arch without systemd) for my laptop as it gives full control over my computer, and Devuan (Debian without systemd) for &lt;a href="https://dev.to/shobanchiddarth/setting-up-pi-hole-as-a-custom-dns-server-on-my-home-lab-4jd7"&gt;my server&lt;/a&gt; for stability. If you are using a Linux distro that is going to implement age verification like Ubuntu or Fedora consult the above tracker to make a decision on the OS that fits your needs and opposes age verification and does not have systemd and switch to it as quick as possible. If you are using Windows switch to Linux, it is a corporate controlled proprietary OS and they already do a lot of spying on you and also Zuckerberg, Bill Gates are all a part of the Epstein class and they are all in this together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is it impossible to enforce this law?
&lt;/h2&gt;

&lt;p&gt;Obviously, Linux is open source and if the OS adds age verification, someone will just fork it and create a "libre" version that doesn't have it. And good luck to government jarheads trying to arrest everyone, you won't have enough room in prison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is it impossible to comply with this law?
&lt;/h2&gt;

&lt;p&gt;Even if every single person in America is willing to comply with this law, it is literally impossible. The law is very very vague about the definitions of an Operating System, an Account, and a User. So almost everything that is technically a "computer" falls into this law. And almost everything that is a computer (that probably runs linux) includes your laptop and desktop and servers (obviously), your smart TV, your smart watch, your calculator (&lt;a href="https://github.com/c3d/db48x/blob/stable/LEGAL-NOTICE.md" rel="noopener noreferrer"&gt;probably&lt;/a&gt;), your "smart" washing machine, fridge, dishwasher and other smart devices, your robot vacuum cleaner, your router, raspberry pis that are part of iot devices, any and all forms of iot devices, your CCTV cameras, your baby and dog monitor, believe it or not switches (the layer 2 network devices) are also technically a computer because they have an OS (Cisco IOS), Nintendo Switch console, the gas station pump, your car probably, the bar code scanner at Walmart, traffic signals, public surveillance cameras, your smoke detector, and a lot of things, the list is so long that it cannot be fit in this blog post.&lt;/p&gt;

&lt;p&gt;If everyone in America wanted to comply with the law by entering their age into all "computers" they own, the entire internet will break, several people will die, and a lot of things will be broken. So many things in this world are computers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Right to Repair
&lt;/h3&gt;

&lt;p&gt;Also it is illegal to comply with this law. &lt;a href="https://www.eff.org/pages/unintended-consequences-fifteen-years-under-dmca" rel="noopener noreferrer"&gt;DMCA Law &lt;/a&gt;in the US states that you are not allowed to use technical skills to circumvent DRM on a device (you are legally not allowed to modify a device you own and paid money for) or to "hack" it to run whatever software you want. You could face 5 years in jail or $500,000 fine for changing the electric circuits of a physical device you purchased with money and own in your hand. &lt;/p&gt;

&lt;p&gt;Nintendo used this law to sue people breaking into Nintendo Switch (the gaming console) to run custom software. BMW implemented subscription based heated seat, using electricity from the battery of the car that you paid for, generated by the fuel you paid for, which costs the company $0 to produce some heat yet they charge users monthly for it. And it is backed by the law, if you circumvent it to have unlimited heated seat then you go to jail. And Tesla implemented a pay walled battery, you have to pay money to use 100% of the battery that you own. BMW is a horrible company by the way they patented BMW screws that can only be purchased from their vendors to make sure you always come to them for repairs and don't repair on your own, that is a completely different story but America is slowly becoming this corpo owned hell hole. &lt;br&gt;
Sources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.theverge.com/2024/3/4/24090357/nintendo-yuzu-emulator-lawsuit-settlement" rel="noopener noreferrer"&gt;https://www.theverge.com/2024/3/4/24090357/nintendo-yuzu-emulator-lawsuit-settlement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.notebookcheck.net/Nintendo-lawsuit-ends-in-2-million-settlement-against-Mig-Switch-seller-accused-of-aiding-piracy.1107589.0.html" rel="noopener noreferrer"&gt;https://www.notebookcheck.net/Nintendo-lawsuit-ends-in-2-million-settlement-against-Mig-Switch-seller-accused-of-aiding-piracy.1107589.0.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arstechnica.com/gaming/2025/05/nintendo-threatens-to-brick-switch-consoles-for-hacking-piracy/" rel="noopener noreferrer"&gt;https://arstechnica.com/gaming/2025/05/nintendo-threatens-to-brick-switch-consoles-for-hacking-piracy/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://insideevs.com/news/601330/tesla-backtracks-on-asking-4500-usd-unlock-model-s-range-after-web-outrage/" rel="noopener noreferrer"&gt;https://insideevs.com/news/601330/tesla-backtracks-on-asking-4500-usd-unlock-model-s-range-after-web-outrage/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://electrek.co/2023/08/15/teslas-new-model-s-x-same-battery-pack-but-with-software-locked-capacity/" rel="noopener noreferrer"&gt;https://electrek.co/2023/08/15/teslas-new-model-s-x-same-battery-pack-but-with-software-locked-capacity/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.techspot.com/news/111208-bmw-admits-heated-seat-subscriptions-mistake-but-commits.html" rel="noopener noreferrer"&gt;https://www.techspot.com/news/111208-bmw-admits-heated-seat-subscriptions-mistake-but-commits.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.repairerdrivennews.com/2025/12/30/bmw-files-patent-for-screw-that-uses-emblem-as-the-drive-structure/" rel="noopener noreferrer"&gt;https://www.repairerdrivennews.com/2025/12/30/bmw-files-patent-for-screw-that-uses-emblem-as-the-drive-structure/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Coming back to what I was saying, it is illegal to open up your robot vacuum cleaner and flash a custom operating system into it, in the place where the current linux distro of it is running, by somehow wiring it up with a keyboard mouse and display. So you are not allowed to do it or you face prison. But the age verification law says you are required to enter your age into all "computers" you own and the only way to enter it into your robot vacuum cleaner is to circumvent its DRM by breaking it open, which is illegal. (this applies to not just robot vacuum cleaners).&lt;/p&gt;

&lt;p&gt;America has finally made it. It is illegal to follow the law. Welcome to land of the free baby, where you are legally not allowed to modify devices you paid money to own and cannot have anonymity and privacy.&lt;/p&gt;

&lt;p&gt;Louis Rossmann (the man behind the clippy movement) has been shouting about the "Right to Repair" for a very long time. Please pay attention to that one: &lt;a href="https://www.youtube.com/@rossmanngroup/videos" rel="noopener noreferrer"&gt;https://www.youtube.com/@rossmanngroup/videos&lt;/a&gt; and &lt;a href="https://consumerrights.wiki/w/Main_Page" rel="noopener noreferrer"&gt;https://consumerrights.wiki/w/Main_Page&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud is up there above us
&lt;/h3&gt;

&lt;p&gt;And almost nobody on the internet has covered the cloud side of things related to age verification.&lt;/p&gt;

&lt;p&gt;Docker containers are also technically a computer since it is an OS (alpine mostly). Are people required to enter their age inside docker containers too? Are you only required to enter your age when pushing a docker container to the internet or whenever creating it like dev or testing? What about Virtual Machines? Amazon Web Services offer an OS called "Amazon Linux" so they are also operating system developers. So by law they are required to implement age attestation in their OS, I am not sure what they are gonna do about it. &lt;/p&gt;

&lt;p&gt;AWS is the biggest cloud provider where majority of production infrastructure lives. Since the law requires every "user" that has an "account" on every "computer" to enter their age, will Jeff Bezos be required to enter his age into every single EC2 instance created in American regions?&lt;/p&gt;

&lt;p&gt;Even if he is willing to, let's assume it takes Jeff Bezos 3 seconds to enter his age into 1 EC2 instance (type 2 numbers + enter key + window switching). So that is &lt;code&gt;86,400/3=28,800&lt;/code&gt;. He will be entering his age into 28,800 EC2 instances per day which is a frighteningly low amount of EC2 instances compared to how much actually exist and is being created every day, assuming he is willing to work 24 hours.&lt;/p&gt;

&lt;p&gt;Most of the real production infra does not have EC2 instances manually created and provisioned from the console, they are all automated using tools like Terraform. And 28,800 EC2 instances does not count the several internal "technically a computer" servers that are used for things like lambda, ECS, Fargate, EKS and there is still a ton more including other cloud providers, on prem data centers many companies use, and also the on prem network devices (like switches and routers I mentioned above). Now you see why the entire internet will break, it is impossible for the owner of the "computer" to enter their age into every "computer" they own due to the laws of physics.&lt;/p&gt;

&lt;p&gt;The law is extremely vague about the definitions either because&lt;a href="https://youtu.be/aJllQ9d3pYM?t=2028" rel="noopener noreferrer"&gt; lawmakers are stupid and think cloud means where we get the rain from&lt;/a&gt; or they are evil and want to strip away as much people's rights as possible and I don't know which is worse or it could be both. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why should someone in India (me, and you, even if you are not in India) care about some American law?
&lt;/h2&gt;

&lt;p&gt;Brazil is a country that is not America yet Facebook pulled strings and got the law passed over there. India is next. Indian government isn't exactly pro consumer rights or pro privacy, law requires VPN services to store logs and share them with the government, that is why ProtonVPN moved away from India. And there were talks about the government banning entire Proton suite from India because some idiot sent a fake bomb threat somewhere using proton email address and they were not able to find him. Call me paranoid but I think the someone is the government making up reasons to ban proton mail.&lt;/p&gt;

&lt;p&gt;And even if the law hypothetically never comes to India, if I am using Ubuntu (thankfully I am not) and Ubuntu pushes an update on the day the law takes effect forcing me to enter my age or else lock my drive, I will have no choice but to either comply with the law (which is bad and is a slippery slope like I said above) or to mount the hard drive, somewhere else, copy the files, and reinstall another OS that doesn't have age verification which will be tedious to do after the law has been implemented, hopping to another distro now is easy. This is the same as disaster recovery in Cloud Computing, we don't wait until disaster happens to recover resources, we plan in advance and provision multi AZ or multi region so if disaster affects on AZ or an entire country we would still have other areas up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's what you are gonna do
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1zjojhvq4exezyeum1k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1zjojhvq4exezyeum1k.jpg" alt="kid-named-finger" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get as many normies to switch to Linux&lt;/li&gt;
&lt;li&gt;Tell everyone you know about age verification&lt;/li&gt;
&lt;li&gt;Increase awareness about the importance of privacy, anonymity, security, and right to repair&lt;/li&gt;
&lt;li&gt;If someone already uses Linux, get them to switch to an OS that doesn't support age verification (see tracker)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>linux</category>
      <category>privacy</category>
      <category>freedom</category>
      <category>security</category>
    </item>
    <item>
      <title>Documenting my Physical Home lab in Packet Tracer</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Tue, 05 May 2026 17:09:00 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/documenting-my-physical-home-lab-in-packet-tracer-n2a</link>
      <guid>https://dev.to/shobanchiddarth/documenting-my-physical-home-lab-in-packet-tracer-n2a</guid>
      <description>&lt;p&gt;&lt;strong&gt;Repo link:&lt;/strong&gt; &lt;a href="https://github.com/ShobanChiddarth/physical-homelab-in-packet-tracer" rel="noopener noreferrer"&gt;github.com/ShobanChiddarth/physical-homelab-in-packet-tracer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I recently finished my 3rd year end sem exams and I am in my summer holidays. I am preparing for the  CCNA certification so I decided to recreate my physical home lab in Cisco Packet Tracer.&lt;/p&gt;

&lt;p&gt;To download the packet tracer lab file you can visit the GitHub repo linked in the top of this post and look at the README.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;I used a WRT300N router to simulate the Tenda AC6 router, and a 1941 (non wireless) router to simulate the Jio Fiber router. I did not need to connect any wireless devices to the Jio router in this packet tracer lab and also in Packet tracer connecting the WAN port (Internet port) of a wireless router to the LAN port of another wireless router wouldn't work like in real life, it refuses to form a connection. So I went with static IPs for the Jio Router's LAN port and Tenda Router's WAN port.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devices inside Tenda router's LAN
&lt;/h3&gt;

&lt;p&gt;As shown in the previous entries of this series, there is a ThinkCenter M81 (represented by Server PT) connected through ethernet to the Tenda router, in which HTTP and DNS services have been enabled (Pi-hole). And my laptop and my smartphone are connected over Wi-Fi to the Tenda router.&lt;/p&gt;

&lt;h3&gt;
  
  
  Devices outside Tenda router's LAN (inside Jio router's LAN)
&lt;/h3&gt;

&lt;p&gt;We will not be worrying about this part as I do not control the Jio router and guests and other family members will be using it. There are mobile phones and a TV connected to it. There was a smart vaccum cleaner connected to it long ago like in the same Jio LAN without any isolation and it stopped working but if I was in charge of managing the network I would have set up proper isolation for IOT devices like this one by putting them on a separate VLAN, setting up guest network and blocking them from communicating with other devices using ACLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internet Access
&lt;/h3&gt;

&lt;p&gt;Real internet is not possible inside Cisco Packet tracer so assume the cloud up above is the real internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  IP Configuration
&lt;/h3&gt;

&lt;p&gt;IP configuration is the exact same as my lab in real life&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jio LAN = &lt;code&gt;192.168.29.1/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tenda WAN = &lt;code&gt;DHCP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tenda LAN = &lt;code&gt;192.168.1.1/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ThinkCenter M81 (Pi-hole) = &lt;code&gt;192.168.1.2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;My Laptop = &lt;code&gt;DHCP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;My Phone = &lt;code&gt;DHCP&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Full Lab
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd0ahw5gyf7ape3352jf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwd0ahw5gyf7ape3352jf.png" alt="001-full-lab" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pi-hole DNS records
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2tm3xveunhjgw8aevvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2tm3xveunhjgw8aevvx.png" alt="002-dns-records" width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pinging ThinkCenter M81 from My Laptop
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qkxjro2slfc3sk9e6l7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qkxjro2slfc3sk9e6l7.png" alt="003-pinging-server" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing Pi-hole dashboard from My Laptop
&lt;/h3&gt;

&lt;p&gt;I edited the default index.html in HTTP server a little bit, this is not an actual computer so installing Pi-hole is not possible. The DNS resolution works that is what is important.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9pe015g23qy2apmq0np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv9pe015g23qy2apmq0np.png" alt="004-pi-hole-from-laptop" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing Pi-hole dashboard from My Phone
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yjows69s6bhkkwo86wj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yjows69s6bhkkwo86wj.png" alt="005-pi-hole-from-phone" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Therefore I successfully created a simulation of my real home lab inside Packet Tracer. I am currently preparing for CCNA certification and this packet tracer lab has helped me map a network whose behaviour I already know onto Packet Tracer's environment. When you build a network from scratch in a simulator, you have no reference point, but when you recreate something you physically own, you immediately notice where the simulator behaves differently from reality and understand its limitations, and that gap is where the actual learning happens.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>cisco</category>
      <category>network</category>
      <category>homelab</category>
    </item>
    <item>
      <title>AWS Terraform infrastructure for a FastAPI+PostgreSQL backend</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Mon, 23 Mar 2026 14:47:31 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/aws-terraform-infrastructure-for-a-fastapipostgresql-backend-3dip</link>
      <guid>https://dev.to/shobanchiddarth/aws-terraform-infrastructure-for-a-fastapipostgresql-backend-3dip</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This blog post showcases the AWS infrastructure written in Terraform for the backend of &lt;code&gt;alumni-connect&lt;/code&gt;, a MVP of a college project written in FastAPI with a PostgreSQL database.&lt;/p&gt;

&lt;p&gt;The terraform repo: &lt;a href="https://github.com/ShobanChiddarth/alumni-connect-terraform" rel="noopener noreferrer"&gt;https://github.com/ShobanChiddarth/alumni-connect-terraform&lt;/a&gt;&lt;br&gt;
The backend repo: &lt;a href="https://github.com/ShobanChiddarth/alumni-connect-backend" rel="noopener noreferrer"&gt;https://github.com/ShobanChiddarth/alumni-connect-backend&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqaopfzeftq2snnl14r3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqaopfzeftq2snnl14r3.png" alt="architecture" width="800" height="645"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  VPC Design
&lt;/h3&gt;

&lt;p&gt;Since this is an MVP, I went with the absolute minimal for everything. 1 VPC exists on &lt;code&gt;ap-south-1&lt;/code&gt;. It has an internet gateway. Frontend is hosted in netlify, so API requests as well as management traffic (SSH) will enter through the internet gateway.&lt;/p&gt;
&lt;h3&gt;
  
  
  Subnet Design
&lt;/h3&gt;

&lt;p&gt;There are 4 subnets in total.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A public subnet to handle management traffic (&lt;code&gt;ap-south-1a&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;2 private subnets for backend and database EC2 instances (&lt;code&gt;ap-south-1a&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Another public subnet to satisfy AWS's hard requirement of multi AZ for load balancers (&lt;code&gt;ap-south-1b&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The management subnet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contains the bastion EC2&lt;/li&gt;
&lt;li&gt;Contains a NAT gateway for EC2 instances in the private subnet to reach the internet&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Load Balancer
&lt;/h3&gt;

&lt;p&gt;The backend EC2 has no public IP and sits in a private subnet. Its security group only accepts port 8000 from the ALB security group - there is no direct path from the internet to the backend. The ALB is the only entry point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;listens on port 80 for HTTP traffic from the public internet&lt;/li&gt;
&lt;li&gt;spans across &lt;code&gt;ap-south-1a&lt;/code&gt; (management subnet) and &lt;code&gt;ap-south-1b&lt;/code&gt; (empty subnet) - AWS requires an internet-facing ALB to span at least 2 AZs&lt;/li&gt;
&lt;li&gt;forwards the traffic from the internet to port 8000 in the backend EC2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Security Groups Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SSH traffic from the internet is allowed only to the bastion EC2&lt;/li&gt;
&lt;li&gt;SSH traffic to the backend and database EC2s are allowed only from the bastion EC2&lt;/li&gt;
&lt;li&gt;HTTP traffic from the internet is allowed only to the load balancer&lt;/li&gt;
&lt;li&gt;HTTP traffic to the backend is allowed only from the load balancer&lt;/li&gt;
&lt;li&gt;Database traffic (port 5432) to the database EC2 is allowed only from the backend EC2&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  SSH Keys
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bastion EC2's SSH private key stays on the local computer of the person deploying this&lt;/li&gt;
&lt;li&gt;Other EC2s SSH private key stays in the bastion&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  EC2 instances
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Backend EC2 is set to pull and run docker image &lt;code&gt;shobanchiddarth/alumni-connect-backend:0.0.2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database EC2 is set to pull and run docker image &lt;code&gt;postgres:16&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  NAT Gateway Cost Problem
&lt;/h2&gt;

&lt;p&gt;I wrote a LinkedIn post about this. &lt;a href="https://www.linkedin.com/posts/shobanchiddarth_terraform-aws-vpc-activity-7441719825703366656-8_15" rel="noopener noreferrer"&gt;Click here to view it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are not able to see it, here are the contents of the post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;I recently faced a problem with making EC2 instances communicate with the internet &lt;span class="k"&gt;while &lt;/span&gt;deploying a FastAPI+Postgres backend architecture on AWS using Terraform. NAT Gateways exist to solve this - a NAT gateway can be placed &lt;span class="k"&gt;in &lt;/span&gt;a public subnet and traffic can be routed through it.

But NAT Gateways are costly. ~&lt;span class="nv"&gt;$0&lt;/span&gt;.045/hr just to exist, plus data transfer charges.

The solution I used: keep the NAT gateway &lt;span class="k"&gt;in &lt;/span&gt;the Terraform code, but destroy it immediately after deployment. Private instances lose internet access after packages are updated, Docker images are pulled, and containers are running.

terraform destroy &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_nat_gateway.alumni-nat-gw &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_eip.nat-gateway-elastic-ip

I also tried a NAT instance - an EC2 that acts as a router - but ran into configuration issues getting it to actually forward traffic. Ended up not going down that path &lt;span class="k"&gt;for &lt;/span&gt;this project.

This temporary NAT gateway approach is good enough &lt;span class="k"&gt;for &lt;/span&gt;a college project MVP with almost no &lt;span class="nb"&gt;users &lt;/span&gt;that gets destroyed &lt;span class="k"&gt;in &lt;/span&gt;a few hours anyway.

Has anyone dealt with this &lt;span class="k"&gt;in &lt;/span&gt;production? Is there a cleaner pattern &lt;span class="k"&gt;for &lt;/span&gt;private subnet internet access beyond NAT gateway or NAT instance?

&lt;span class="c"&gt;#Terraform #AWS #VPC #CloudComputing #IaC #CloudCost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Steps to Deploy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Clone the repo&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ShobanChiddarth/alumni-connect-terraform
&lt;span class="nb"&gt;cd &lt;/span&gt;alumni-connect-terraform/infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set AWS credentials&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the database password&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TF_VAR_db_password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Destroy the NAT gateway after apply&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_nat_gateway.alumni-nat-gw &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aws_eip.nat-gateway-elastic-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To tear down everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Proof of Deployment
&lt;/h2&gt;

&lt;p&gt;These are screenshots I took when it was live. I then destroyed the whole infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnndb82s8biv5na3ptr8q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnndb82s8biv5na3ptr8q.png" alt="working-001" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frza18lpz5whs0lvpp6j7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frza18lpz5whs0lvpp6j7.png" alt="working-002" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tpcouffa30zv5ncpdvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tpcouffa30zv5ncpdvl.png" alt="working-003" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbhdauhnjnr5ro7hwp81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbhdauhnjnr5ro7hwp81.png" alt="working-004" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I archived the API landing URL (first image) in Wayback Machine. Here is the link: &lt;a href="https://web.archive.org/web/20260322130840/http://alumni-alb-backend-83642402.ap-south-1.elb.amazonaws.com/" rel="noopener noreferrer"&gt;https://web.archive.org/web/20260322130840/http://alumni-alb-backend-83642402.ap-south-1.elb.amazonaws.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This is the full Terraform infrastructure for the &lt;code&gt;alumni-connect&lt;/code&gt; backend.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>cloud</category>
      <category>cloudcomputing</category>
    </item>
    <item>
      <title>IAM Development Lab in Keycloak</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Fri, 20 Mar 2026 17:17:13 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/iam-development-lab-in-keycloak-19i7</link>
      <guid>https://dev.to/shobanchiddarth/iam-development-lab-in-keycloak-19i7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is the ultimate IAM development and employee lifecycle management lab I am doing where I will be making use of the skills I learnt from the &lt;a href="https://www.theforage.com/completion-certificates/ifobHAoMjQs9s6bKS/gmf3ypEXBj2wvfQWC_ifobHAoMjQs9s6bKS_69a0eaeeb1f8e4b8685709b8_1772548764230_completion_certificate.pdf" rel="noopener noreferrer"&gt;TATA virtual internship via Forage: Cybersecurity Analyst - IAM Developer&lt;/a&gt; to develop and implement an IAM solution for a fictional organization.&lt;/p&gt;

&lt;p&gt;I chose Keycloak for this lab instead of SailPoint with Oracle Identity Manager because Keycloak is fully open source. This is a standalone lab independent of OpenLDAP.&lt;/p&gt;

&lt;p&gt;The internship covered&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM fundamentals&lt;/li&gt;
&lt;li&gt;Digital identity&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;SSO&lt;/li&gt;
&lt;li&gt;least privilege principle
And then walked through designing and planning a full IAM implementation for a fictional enterprise called TechCorp (but the fictional org we will be representing is called Acme). The proposed solution used &lt;/li&gt;
&lt;li&gt;SailPoint for automated user lifecycle management and&lt;/li&gt;
&lt;li&gt;Oracle Identity Manager for RBAC
with a four-phase implementation plan covering deployment, testing, training, and ongoing monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lab takes those same requirements and implements them end-to-end:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A structured org with departments and roles&lt;/li&gt;
&lt;li&gt;RBAC enforced at the role level&lt;/li&gt;
&lt;li&gt;Full user lifecycle management from provisioning to offboarding&lt;/li&gt;
&lt;li&gt;MFA&lt;/li&gt;
&lt;li&gt;SSO via OIDC&lt;/li&gt;
&lt;li&gt;Email-based verification flows&lt;/li&gt;
&lt;li&gt;Brute force protection&lt;/li&gt;
&lt;li&gt;Audit logging 
All running on-premises in VirtualBox with no external dependencies.
## Pre-Requisites&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/the-superior-way-to-make-vms-communicate-with-each-other-as-well-as-host-with-internet-access-42m1"&gt;Superior VM Intercommunication setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/setting-up-pi-hole-as-a-custom-dns-server-on-my-home-lab-4jd7"&gt;Pi-Hole&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/setting-up-ssl-https-on-my-home-lab-g45"&gt;Mkcert Local CA TLS (for HTTPS)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build Log
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keycloak Server Initialization
&lt;/h3&gt;

&lt;p&gt;I cloned a debian server VM, put it in the current VirtualBox host only network I have, assigned a static IP (&lt;code&gt;192.168.57.8&lt;/code&gt;), Then I edited its hostname, and mapped DNS record &lt;code&gt;keycloak.acme.internal -&amp;gt; 192.168.57.8&lt;/code&gt; in Pi-Hole.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ifconfig enp0s3
&lt;span class="gp"&gt;enp0s3: flags=4163&amp;lt;UP,BROADCAST,RUNNING,MULTICAST&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;mtu 1500
&lt;span class="go"&gt;        inet 192.168.57.8  netmask 255.255.255.0  broadcast 192.168.57.255
&lt;/span&gt;&lt;span class="gp"&gt;        inet6 fe80::43b5:ca52:a96d:134a  prefixlen 64  scopeid 0x20&amp;lt;link&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;        ether 08:00:27:8a:4a:29  txqueuelen 1000  (Ethernet)
        RX packets 28569  bytes 3772704 (3.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 28550  bytes 2712767 (2.5 MiB)
        TX errors 0  dropped 2 overruns 0  carrier 0  collisions 0

&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nslookup keycloak.acme.internal
&lt;span class="go"&gt;Server:     192.168.57.3
&lt;/span&gt;&lt;span class="gp"&gt;Address:    192.168.57.3#&lt;/span&gt;53
&lt;span class="go"&gt;
Name:   keycloak.acme.internal
Address: 192.168.57.8

&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I installed java on it because Keycloak requires it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;default-jre &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java version 21 btw&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;java &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;openjdk 21.0.10 2026-01-20
OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1)
OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I downloaded Keycloak from GitHub, extracted it and put it in &lt;code&gt;/opt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~/Downloads$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;keycloak-26.5.6.tar.gz
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/Downloads$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; keycloak-26.5.6.tar.gz 
&lt;span class="gp"&gt;debian@keycloak:~/Downloads$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;keycloak-26.5.6  keycloak-26.5.6.tar.gz
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/Downloads$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo mv &lt;/span&gt;keycloak-26.5.6 /opt/keycloak
&lt;span class="gp"&gt;debian@keycloak:~/Downloads$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt
&lt;span class="gp"&gt;debian@keycloak:/opt$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;keycloak
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:/opt$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And created a new user and set appropriate permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /sbin/nologin keycloak
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; keycloak:keycloak /opt/keycloak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local CA certificates for Keycloak
&lt;/h3&gt;

&lt;p&gt;I did the usual, created certs for &lt;code&gt;keycloak.acme.internal&lt;/code&gt; and moved them to the Keycloak server and pointed Keycloak to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;keycloak.acme.internal-key.pem  keycloak.acme.internal.pem
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/keycloak/conf/certs
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;keycloak.acme.internal.pem /opt/keycloak/conf/certs/keycloak.crt
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;keycloak.acme.internal-key.pem /opt/keycloak/conf/certs/keycloak.key
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; keycloak:keycloak /opt/keycloak/conf/certs
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;640 /opt/keycloak/conf/certs/keycloak.key
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /opt/keycloak/conf/keycloak.conf 
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /opt/keycloak/conf/
&lt;span class="go"&gt;cache-ispn.xml  certs/          keycloak.conf   README.md       truststores/    
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /opt/keycloak/conf/keycloak.conf | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;span class="go"&gt;hostname=keycloak.acme.internal
https-certificate-file=/opt/keycloak/conf/certs/keycloak.crt
https-certificate-key-file=/opt/keycloak/conf/certs/keycloak.key
http-enabled=false

&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keycloak initial build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; keycloak &lt;span class="nv"&gt;KEYCLOAK_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="nv"&gt;KEYCLOAK_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin123 /opt/keycloak/bin/kc.sh build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; keycloak &lt;span class="nv"&gt;KEYCLOAK_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="nv"&gt;KEYCLOAK_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password /opt/keycloak/bin/kc.sh build
&lt;span class="go"&gt;WARNING: Usage of the default value for the db option in the production profile is deprecated. Please explicitly set the db instead.
INFO: The following run time options were found, but will be ignored during build time: kc.https-certificate-key-file, kc.http-enabled, kc.hostname, kc.https-certificate-file

Updating the configuration and installing your custom providers, if any. Please wait.
2026-03-19 20:00:27,827 INFO  [io.quarkus.deployment.QuarkusAugmentor] (main) Quarkus augmentation completed in 10529ms
Server configuration updated and persisted. Run the following command to review the configuration:

    kc.sh show-config

&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/opt/keycloak/bin/kc.sh show-config
&lt;span class="go"&gt;Current Mode: production
Current Configuration:
    kc.log-level-org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup =  null (Derived)
    kc.log-level-io.quarkus.config =  null (Derived)
    kc.hostname =  keycloak.acme.internal (keycloak.conf)
    kc.log-console-output =  default (classpath application.properties)
    kc.log-level-io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor =  null (Derived)
    kc.log-level-liquibase.database.core.PostgresDatabase =  null (Derived)
    kc.optimized =  true (Persisted)
    kc.version =  26.5.6 (SysPropConfigSource)
    kc.log-level-io.smallrye.openapi.runtime.scanner.dataobject =  null (Derived)
    kc.https-certificate-file =  /opt/keycloak/conf/certs/keycloak.crt (keycloak.conf)
    kc.log-level-org.jboss.resteasy.resteasy_jaxrs.i18n =  null (Derived)
    kc.log-level-io.quarkus.arc.processor.BeanArchives =  null (Derived)
    kc.log-level-io.quarkus.deployment.steps.ReflectiveHierarchyStep =  null (Derived)
    kc.http-enabled =  false (keycloak.conf)
    kc.log-level-org.hibernate.SQL_SLOW =  null (Derived)
    kc.https-certificate-key-file =  /opt/keycloak/conf/certs/keycloak.key (keycloak.conf)
    kc.log-level-io.quarkus.arc.processor.IndexClassLookupUtils =  null (Derived)
    kc.log-file =  /opt/keycloak/bin/../data/log/keycloak.log (classpath application.properties)
    kc.log-level-org.hibernate.engine.jdbc.spi.SqlExceptionHelper =  null (Derived)
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keycloak daemon build (systemd)
&lt;/h3&gt;

&lt;p&gt;I created a file &lt;code&gt;/etc/systemd/system/keycloak.service&lt;/code&gt; with contents&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="nt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;Keycloak Identity and Access Management
&lt;span class="nt"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;network.target

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;keycloak
&lt;span class="nt"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;keycloak
&lt;span class="nt"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;KEYCLOAK_ADMIN=admin
&lt;span class="nt"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;KEYCLOAK_ADMIN_PASSWORD=admin123
&lt;span class="nt"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/opt/keycloak/bin/kc.sh start
&lt;span class="nt"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;on-failure
&lt;span class="nt"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;10

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="nt"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And enabled the service&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; keycloak
&lt;span class="go"&gt;Created symlink '/etc/systemd/system/multi-user.target.wants/keycloak.service' → '/etc/systemd/system/keycloak.service'.
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status keycloak
&lt;span class="go"&gt;● keycloak.service - Keycloak Identity and Access Management
&lt;/span&gt;&lt;span class="gp"&gt;     Loaded: loaded (/etc/systemd/system/keycloak.service;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;enabled&lt;span class="p"&gt;;&lt;/span&gt; preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;     Active: active (running) since Thu 2026-03-19 20:05:40 IST;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;6s ago
&lt;span class="go"&gt; Invocation: cde0a8f017a24bac8dd23934aa7a5bf2
   Main PID: 3250 (kc.sh)
      Tasks: 29 (limit: 2301)
     Memory: 269.9M (peak: 270.2M)
        CPU: 6.151s
     CGroup: /system.slice/keycloak.service
             ├─3250 /bin/sh /opt/keycloak/bin/kc.sh start
&lt;/span&gt;&lt;span class="gp"&gt;             └─3322 java -Djava.util.concurrent.ForkJoinPool.common.threadFactory=io.quarkus.bootstrap.forkjoin.QuarkusForkJoinW&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
Mar 19 20:05:40 keycloak.acme.internal systemd[1]: Started keycloak.service - Keycloak Identity and Access Management.
&lt;/span&gt;&lt;span class="gp"&gt;Mar 19 20:05:46 keycloak.acme.internal kc.sh[3322]: 2026-03-19 20:05:46,328 INFO  [org.hibernate.orm.jdbc.batch] (JPA Startup Th&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keycloak admin dashboard
&lt;/h3&gt;

&lt;p&gt;I visited &lt;code&gt;https://keycloak.acme.internal:8443&lt;/code&gt; from where the &lt;code&gt;rootCA.pem&lt;/code&gt; was trusted in the OS as well as browser certificate store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe55x1btu531vml9t709u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe55x1btu531vml9t709u.png" alt="001" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then signed in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7kl5xkwrryli4kjt0ug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7kl5xkwrryli4kjt0ug.png" alt="002" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then created a permanent admin account as it says. I created a new user with username &lt;code&gt;administrator&lt;/code&gt;, set a password, and then assigned &lt;code&gt;admin&lt;/code&gt; role to it. Then logged in with that user and deleted the temporary admin user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3mr94tnqaboqlu63a6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3mr94tnqaboqlu63a6v.png" alt="003" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I created a realm for our fictional org - &lt;code&gt;acme&lt;/code&gt; - with display name "Acme Corp", user self-registration disabled (admin-provisioned org), and email verification enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mailpit initial setup
&lt;/h3&gt;

&lt;p&gt;Since this is a local lab with no real mail infrastructure, I used Mailpit - a lightweight fake SMTP server that catches all outbound emails and displays them in a web UI instead of actually delivering them. This lets me test Keycloak's email flows (verification, password reset, OTP) without setting up a real mail server or domain. I ran it on the same VM as Keycloak since it's a single binary with minimal resource usage.&lt;/p&gt;

&lt;p&gt;I installed mailpit from GitHub, on the same VM as Keycloak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://github.com/axllent/mailpit/releases/download/v1.29.3/mailpit-linux-amd64.tar.gz
&lt;span class="nb"&gt;mkdir &lt;/span&gt;mailpit
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; mailpit-linux-amd64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; mailpit
&lt;span class="nb"&gt;cd &lt;/span&gt;mailpit
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;mailpit /usr/local/bin/
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/mailpit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then verified it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mailpit version
&lt;span class="go"&gt;mailpit v1.29.3 compiled with go1.26.1 on linux/amd64
Error checking for latest release: failed to fetch releases: received status code 403
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I created a new user for it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;useradd &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /sbin/nologin mailpit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mailpit daemon build (systemd)
&lt;/h3&gt;

&lt;p&gt;I put the following contents on &lt;code&gt;/etc/systemd/system/mailpit.service&lt;/code&gt; (copied the previously generated keys to &lt;code&gt;/etc/mailpit/certs&lt;/code&gt; and changed ownership of that folder to &lt;code&gt;mailpit&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/mailpit.service&lt;/span&gt;
&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="nt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;Mailpit SMTP and Web UI
&lt;span class="nt"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;network.target

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;mailpit
&lt;span class="nt"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;mailpit
&lt;span class="nt"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/usr/local/bin/mailpit --smtp 0.0.0.0:1025 --listen 0.0.0.0:8025 --ui-tls-cert /etc/mailpit/certs/keycloak.acme.internal.pem --ui-tls-key /etc/mailpit/certs/keycloak.acme.internal-key.pem
&lt;span class="nt"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;on-failure
&lt;span class="nt"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;10

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="nt"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And ran&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; mailpit
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mailpit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@keycloak:~/Downloads/mailpit$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="go"&gt;sudo systemctl enable --now mailpit
sudo systemctl status mailpit
Created symlink '/etc/systemd/system/multi-user.target.wants/mailpit.service' → '/etc/systemd/system/mailpit.service'.
● mailpit.service - Mailpit SMTP and Web UI
&lt;/span&gt;&lt;span class="gp"&gt;     Loaded: loaded (/etc/systemd/system/mailpit.service;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;enabled&lt;span class="p"&gt;;&lt;/span&gt; preset: enab&amp;gt;
&lt;span class="gp"&gt;     Active: active (running) since Fri 2026-03-20 15:37:36 IST;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;67ms ago
&lt;span class="go"&gt; Invocation: 1046374b8fb24c4f92c8a6fe9683cb5f
   Main PID: 2010 (mailpit)
      Tasks: 3 (limit: 2301)
     Memory: 8.7M (peak: 8.7M)
        CPU: 13ms
     CGroup: /system.slice/mailpit.service
&lt;/span&gt;&lt;span class="gp"&gt;             └─2010 /usr/local/bin/mailpit --smtp 0.0.0.0:1025 --listen 0.0.0.0&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;Mar 20 15:37:36 keycloak.acme.internal systemd[1]: Started mailpit.service - Ma&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;Mar 20 15:37:36 keycloak.acme.internal mailpit[2010]: time="2026/03/20 15:37:36&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;Mar 20 15:37:36 keycloak.acme.internal mailpit[2010]: time="2026/03/20 15:37:36&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;Mar 20 15:37:36 keycloak.acme.internal mailpit[2010]: time="2026/03/20 15:37:36&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;Mar 20 15:37:36 keycloak.acme.internal mailpit[2010]: time="2026/03/20 15:37:36&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;debian@keycloak:~/Downloads/mailpit$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keycloak to use Mailpit for email
&lt;/h3&gt;

&lt;p&gt;After I created the ACME Corp realm on Keycloak, I went to Realm Settings, email, entered these values to make use of Mailpit as email service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcet5cwnc211opa6znxde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcet5cwnc211opa6znxde.png" alt="004" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p0v73mtd560a42572ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p0v73mtd560a42572ce.png" alt="005" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I visited Mailpit dashboard at &lt;code&gt;https://keycloak.acme.internal:8025/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh50n687325i63kxcybo7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh50n687325i63kxcybo7.png" alt="006" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjtaj5wuvvplt6mxn7fq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjtaj5wuvvplt6mxn7fq.png" alt="007" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Password Policy in Keycloak
&lt;/h3&gt;

&lt;p&gt;I went to Authentication -&amp;gt; Password Policy -&amp;gt; Password Policies and set the password requirements to have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimum 10 characters&lt;/li&gt;
&lt;li&gt;Minimum 1 Uppercase&lt;/li&gt;
&lt;li&gt;Minimum 1 Lowercase&lt;/li&gt;
&lt;li&gt;Minimum 1 Digit&lt;/li&gt;
&lt;li&gt;Minimum 1 Special Character&lt;/li&gt;
&lt;li&gt;Password history: 3 (prevents reuse of last 3 passwords)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cmmnrwu4fvc7y1tzo7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cmmnrwu4fvc7y1tzo7x.png" alt="008" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also configured brute force detection under Realm Settings -&amp;gt; Security Defenses -&amp;gt; Brute force detection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brute Force Mode: Lockout permanently after temporary lockout&lt;/li&gt;
&lt;li&gt;Max login failures: 5&lt;/li&gt;
&lt;li&gt;Maximum temporary lockouts: 1&lt;/li&gt;
&lt;li&gt;Strategy to increase wait time: Multiple&lt;/li&gt;
&lt;li&gt;Wait increment: 1 minute&lt;/li&gt;
&lt;li&gt;Max wait: 15 minutes&lt;/li&gt;
&lt;li&gt;Failure reset time: 12 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After 5 failed login attempts the account locks out temporarily. If login failures continue through the temporary lockout, the account gets permanently locked and requires an admin to manually re-enable it. This will be demonstrated later in the user lifecycle section using Burp Suite to capture the login request and a Python script to simulate a brute force attack with a wordlist.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9ayn2da5cbsjpn18v2z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9ayn2da5cbsjpn18v2z.png" alt="009" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Organization Structure
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwapq5izddmehgkowc6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwapq5izddmehgkowc6l.png" alt="org-structure" width="669" height="744"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created the above organization structure in Keycloak - four groups for departments, three realm roles (&lt;code&gt;employee&lt;/code&gt;, &lt;code&gt;manager&lt;/code&gt;, &lt;code&gt;it-admin&lt;/code&gt;), and seven users provisioned and assigned accordingly.&lt;/p&gt;

&lt;p&gt;All users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep3efg6ifyqsauwq0rbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep3efg6ifyqsauwq0rbz.png" alt="010-all-users" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Group Engineering:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxehypmuxnz6t7w18lm98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxehypmuxnz6t7w18lm98.png" alt="011-group-engineering" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Group Finance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t4ci4xlr0noaeismyxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t4ci4xlr0noaeismyxa.png" alt="012-group-finance" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Group HR:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih3vw61cu0cb3hu8yoh9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih3vw61cu0cb3hu8yoh9.png" alt="013-group-hr" width="799" height="483"&gt;&lt;/a&gt;.png)&lt;/p&gt;

&lt;p&gt;Group IT:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjzgn17s3vm81xvyzb65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftjzgn17s3vm81xvyzb65.png" alt="014-group-it" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Role employee:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flblwvqr0dtzn4nrdqckm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flblwvqr0dtzn4nrdqckm.png" alt="015-role-employee" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Role manager:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4shg27qxk1deaiyt9tt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4shg27qxk1deaiyt9tt.png" alt="016-role-manager" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Role IT Admin:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foshy0g5ahzgivji4ddte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foshy0g5ahzgivji4ddte.png" alt="017-role-it-admin" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing MFA via TOTP (Google Authenticator)
&lt;/h3&gt;

&lt;p&gt;I set up password based logins for users (Something You Know). Now I will set up Google Authenticator based 2FA (Something You Have). For this lab, I will be using &lt;a href="https://gauth.apps.gbraad.nl" rel="noopener noreferrer"&gt;gauth.apps.gbraad.nl&lt;/a&gt;, a browser based Google Authenticator app implementation, on the host machine, as my Google Authenticator client.&lt;/p&gt;

&lt;p&gt;I duplicated the default &lt;code&gt;browser&lt;/code&gt; authentication flow in Authentication -&amp;gt; Flows, named it &lt;code&gt;acme browser&lt;/code&gt;, and set the &lt;strong&gt;Browser - Conditional OTP&lt;/strong&gt; step from &lt;code&gt;Conditional&lt;/code&gt; to &lt;code&gt;Required&lt;/code&gt;. Then under Authentication -&amp;gt; Bindings, I set the Browser flow to &lt;code&gt;acme browser&lt;/code&gt;. This forces TOTP on every login for all users in the &lt;code&gt;acme&lt;/code&gt; realm.&lt;/p&gt;

&lt;p&gt;I also enabled the following required actions under Authentication -&amp;gt; Required actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update Password&lt;/li&gt;
&lt;li&gt;Verify Email&lt;/li&gt;
&lt;li&gt;Configure OTP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are applied to all users, so on first login every user is forced to change their temporary password, verify their email via Mailpit, and set up TOTP before they can access anything.&lt;/p&gt;

&lt;p&gt;jsmith is forced to setup Google Authenticator login in next successful login:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2pzt50qq38ndd88fy6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2pzt50qq38ndd88fy6c.png" alt="018-jsmith-2fa" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;jsmith added key to Google Authenticator&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5gdna8czvrxwwq0refe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5gdna8czvrxwwq0refe.png" alt="019-jsmith-ga" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;jsmith enters the OTP value and logs in successfully. Every other account did the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj2v34mvx9iaa3g0tpr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj2v34mvx9iaa3g0tpr9.png" alt="020-everyone-2fa" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  OIDC Client and SSO Demo
&lt;/h3&gt;

&lt;p&gt;To demonstrate SSO, I registered a client in the &lt;code&gt;acme&lt;/code&gt; realm pointing to the official Keycloak demo app hosted at &lt;code&gt;https://www.keycloak.org/app/&lt;/code&gt;. Since it's a static page that runs entirely in the browser, it talks directly to my local Keycloak instance - no server-side component needed.&lt;/p&gt;

&lt;p&gt;Client settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client type: OpenID Connect&lt;/li&gt;
&lt;li&gt;Client authentication: Off (public client)&lt;/li&gt;
&lt;li&gt;Valid redirect URIs: &lt;code&gt;https://www.keycloak.org/app/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Valid post logout redirect URIs: &lt;code&gt;https://www.keycloak.org/app/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Web origins: &lt;code&gt;https://www.keycloak.org&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhjevx6b1ncsmbv1ycgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhjevx6b1ncsmbv1ycgg.png" alt="021-client-setup" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the demo app, I pointed it at my Keycloak instance by entering the server URL, realm, and client ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvu6qcd0j6wg70fied7cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvu6qcd0j6wg70fied7cb.png" alt="022-client-config" width="799" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking Sign In redirected to the Keycloak login page, went through the full authentication flow - password then TOTP - and landed back on the demo app showing jsmith's authenticated session with the decoded token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivuelz6swndezxbotobp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fivuelz6swndezxbotobp.png" alt="023-jsmith-sso-success" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Employee lifecycle management
&lt;/h3&gt;

&lt;p&gt;To demonstrate the full employee lifecycle, I walked through four real-world IAM events using &lt;code&gt;mclark&lt;/code&gt; from the HR department.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. mclark gets promoted to HR manager
&lt;/h4&gt;

&lt;p&gt;mclark was promoted from HR employee to HR manager. In Keycloak, this meant removing the &lt;code&gt;employee&lt;/code&gt; role and assigning &lt;code&gt;manager&lt;/code&gt; under the user's Role Mappings tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca4h7pbli07yv66eijhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fca4h7pbli07yv66eijhi.png" alt="024-before-mclark-promotion" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2u1tfn5ygtwn8vrr2h92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2u1tfn5ygtwn8vrr2h92.png" alt="025-after-mclark-promotion" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. mclark takes a vacation (account disabled)
&lt;/h4&gt;

&lt;p&gt;mclark went on an extended leave of absence. Rather than deleting the account, it was disabled - access is immediately revoked but the account and its data remain intact for when the employee returns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnioetjh74v6adxb6f7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnioetjh74v6adxb6f7j.png" alt="026-mclark-vacation" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. mclark returns (account enabled)
&lt;/h4&gt;

&lt;p&gt;mclark returned from leave. The account was re-enabled, restoring access with all roles and group memberships unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3shapzh3jjwptdjakpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3shapzh3jjwptdjakpz.png" alt="027-mclark-returns" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. mclark quits (account deleted)
&lt;/h4&gt;

&lt;p&gt;mclark resigned. The account was permanently deleted from the realm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmation&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pmkavkenq9kinlch89l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pmkavkenq9kinlch89l.png" alt="028-delete-mclark-confirmation" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mclark is no more&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcxx736qrnknx78ooku3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcxx736qrnknx78ooku3.png" alt="029-no-mclark" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Brute Force Protection
&lt;/h3&gt;

&lt;p&gt;Now I am going to do a brute force attack on jsmith's account. I am going to manually type in some random password values and hit enter multiple times to see what happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6zs7id9uzwlew5tamhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6zs7id9uzwlew5tamhe.png" alt="030-jsmith-temp-locked" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see from the admin dashboard, jsmith is now temporarily locked. &lt;/p&gt;

&lt;h3&gt;
  
  
  Event log
&lt;/h3&gt;

&lt;p&gt;Keycloak captures two categories of events per realm. Login events record every authentication-related action - successful logins, failed attempts, logouts, and token operations. Admin events record every administrative action performed on the realm - user creation, role assignments, deletions, client configuration changes, and so on. Both are visible under Events in the admin console.&lt;/p&gt;

&lt;h4&gt;
  
  
  Login Events
&lt;/h4&gt;

&lt;p&gt;Here is a snippet of lwilson signin related events&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femyldtl3nqtoa7qdilsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Femyldtl3nqtoa7qdilsw.png" alt="031-lwilson-signin" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Admin Events
&lt;/h4&gt;

&lt;p&gt;Here is a snippet of admin events related to jsmith getting promoted&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feys5z3clmsngpt4v9p9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feys5z3clmsngpt4v9p9v.png" alt="032-jsmith-gets-promoted" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The internship asked for a designed IAM solution and an implementation plan. This lab is that plan executed.&lt;/p&gt;

&lt;p&gt;Every requirement from the Tata Forage simulation is covered here in a working deployment: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keycloak handles identity and access management for Acme Corp the same way SailPoint and Oracle Identity Manager would handle it for TechCorp.&lt;/li&gt;
&lt;li&gt;RBAC is enforced through realm roles mapped to job functions.&lt;/li&gt;
&lt;li&gt;User provisioning and de-provisioning follow the same lifecycle model - onboarding with required actions, role changes on promotion, account suspension on leave, and full deletion on resignation.&lt;/li&gt;
&lt;li&gt;MFA via TOTP enforces the "something you know + something you have" authentication standard.&lt;/li&gt;
&lt;li&gt;SSO via OIDC demonstrates seamless access across applications from a single authenticated session.&lt;/li&gt;
&lt;li&gt;Mailpit handles all email flows locally.&lt;/li&gt;
&lt;li&gt;Brute force protection is configured and verified.&lt;/li&gt;
&lt;li&gt;Admin and login events are captured in the audit log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire stack - Keycloak, Mailpit, TLS via a local CA, DNS via Pi-hole, and network routing via pfSense - runs self-contained in VirtualBox, making it fully reproducible as a home lab without any cloud dependency or paid tooling.&lt;/p&gt;

</description>
      <category>security</category>
      <category>iam</category>
      <category>keycloak</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Integrating a local mail server into my LDAP lab</title>
      <dc:creator>Shoban Chiddarth</dc:creator>
      <pubDate>Thu, 19 Mar 2026 07:38:19 +0000</pubDate>
      <link>https://dev.to/shobanchiddarth/integrating-a-local-mail-server-into-my-ldap-lab-4h5f</link>
      <guid>https://dev.to/shobanchiddarth/integrating-a-local-mail-server-into-my-ldap-lab-4h5f</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/ShobanChiddarth/postfix-dovecot-mailserver-in-openldap-lab" rel="noopener noreferrer"&gt;GitHub Repo: @ShobanChiddarth/postfix-dovecot-mailserver-in-openldap-lab&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is the second stage of my &lt;a href="https://dev.to/shobanchiddarth/openldap-home-lab-cyber-security-technical-write-up-4g42"&gt;OpenLDAP home lab&lt;/a&gt;. I set up a mail server to work with existing LDAP configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-Requisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/the-superior-way-to-make-vms-communicate-with-each-other-as-well-as-host-with-internet-access-42m1"&gt;VM Intercommunication setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/setting-up-pi-hole-as-a-custom-dns-server-on-my-home-lab-4jd7"&gt;Pi-hole&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/setting-up-ssl-https-on-my-home-lab-g45"&gt;Local TLS for HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/shobanchiddarth/openldap-home-lab-cyber-security-technical-write-up-4g42"&gt;OpenLDAP Home Lab&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mail Server Install
&lt;/h3&gt;

&lt;p&gt;In the existing setup, I cloned another debian server VM, gave it static IP &lt;code&gt;192.168.57.7&lt;/code&gt;, set its hostname to &lt;code&gt;mail-server.acme.internal&lt;/code&gt; and then added the DNS record in Pi-hole. Then I went ahead and installed the required packages for postfix and dovecot to work with LDAP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postfix postfix-ldap dovecot-core dovecot-imapd dovecot-ldap dovecot-lmtpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I chose "Internet Site" in the postfix install prompt asking about which type of mail server to install and set the domain to &lt;code&gt;acme.internal&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postfix Config
&lt;/h3&gt;

&lt;p&gt;I edited &lt;code&gt;/etc/postfix/main.cf&lt;/code&gt; to have this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;myhostname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;mail-server.acme.internal&lt;/span&gt;
&lt;span class="py"&gt;mydomain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;acme.internal&lt;/span&gt;
&lt;span class="py"&gt;mydestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$myhostname, $mydomain, localhost&lt;/span&gt;
&lt;span class="py"&gt;mynetworks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;192.168.57.0/24 127.0.0.0/8&lt;/span&gt;
&lt;span class="py"&gt;inet_interfaces&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
&lt;span class="py"&gt;mailbox_transport&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;lmtp:unix:private/dovecot-lmtp&lt;/span&gt;
&lt;span class="py"&gt;smtpd_sasl_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dovecot&lt;/span&gt;
&lt;span class="py"&gt;smtpd_sasl_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;private/auth&lt;/span&gt;
&lt;span class="py"&gt;smtpd_sasl_auth_enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;local_recipient_maps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ldap:/etc/postfix/ldap-recipients.cf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mailbox_transport&lt;/code&gt; hands mail delivery off to Dovecot via LMTP. The &lt;code&gt;smtpd_sasl_*&lt;/code&gt; settings let Postfix use Dovecot for SMTP authentication. &lt;code&gt;local_recipient_maps&lt;/code&gt; tells Postfix to look up valid recipients in LDAP instead of the local user table - without this, Postfix rejects mail to LDAP users with "User unknown in local recipient table".&lt;/p&gt;

&lt;p&gt;I created &lt;code&gt;/etc/postfix/ldap-recipients.cf&lt;/code&gt; for the recipient lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;server_host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ldaps://ldap-server.acme.internal&lt;/span&gt;
&lt;span class="py"&gt;bind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="py"&gt;bind_dn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;cn=sssd,ou=service-accounts,dc=acme,dc=internal&lt;/span&gt;
&lt;span class="py"&gt;bind_pw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;SSSDPass123&lt;/span&gt;
&lt;span class="py"&gt;search_base&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ou=users,dc=acme,dc=internal&lt;/span&gt;
&lt;span class="py"&gt;query_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=posixAccount)(mail=%s))&lt;/span&gt;
&lt;span class="py"&gt;result_attribute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;mail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reloaded Postfix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Local CA Config on Mail Server
&lt;/h3&gt;

&lt;p&gt;I copied &lt;code&gt;rootCA.pem&lt;/code&gt; that I created earlier to this server and trusted it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@mail-server:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;rootCA.pem
&lt;/span&gt;&lt;span class="gp"&gt;debian@mail-server:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt
&lt;span class="go"&gt;[sudo] password for debian: 
&lt;/span&gt;&lt;span class="gp"&gt;debian@mail-server:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;update-ca-certificates
&lt;span class="go"&gt;Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL
&lt;/span&gt;&lt;span class="gp"&gt;1 added, 0 removed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="go"&gt;Running hooks in /etc/ca-certificates/update.d...
done.
&lt;/span&gt;&lt;span class="gp"&gt;debian@mail-server:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dovecot TLS Config
&lt;/h3&gt;

&lt;p&gt;I generated a TLS cert for &lt;code&gt;mail-server.acme.internal&lt;/code&gt; using mkcert and copied it to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;mail-server.acme.internal.pem /etc/dovecot/private/mail-server.crt
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;mail-server.acme.internal-key.pem /etc/dovecot/private/mail-server.key
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;root:dovecot /etc/dovecot/private/mail-server.key
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;640 /etc/dovecot/private/mail-server.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then updated &lt;code&gt;/etc/dovecot/conf.d/10-ssl.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ssl_server_cert_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/etc/dovecot/private/mail-server.crt&lt;/span&gt;
&lt;span class="py"&gt;ssl_server_key_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/etc/dovecot/private/mail-server.key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dovecot LDAP Config
&lt;/h3&gt;

&lt;p&gt;I changed dovecot auth settings to use LDAP auth instead of system auth by editing &lt;code&gt;/etc/dovecot/conf.d/10-auth.conf&lt;/code&gt; - commented out &lt;code&gt;auth-system.conf.ext&lt;/code&gt; and uncommented &lt;code&gt;auth-ldap.conf.ext&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;debian@mail-server:~/acme-certs$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/dovecot/conf.d/10-auth.conf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"include auth"&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-deny.conf.ext
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-master.conf.ext
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-oauth2.conf.ext
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-system.conf.ext
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-sql.conf.ext
&lt;span class="go"&gt;!include auth-ldap.conf.ext
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-passwdfile.conf.ext
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;include auth-static.conf.ext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dovecot 2.4 changed the config syntax from older versions, so the standard examples online did not work. After checking the example config the package ships with, I set &lt;code&gt;/etc/dovecot/conf.d/auth-ldap.conf.ext&lt;/code&gt; to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;ldap_uris&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ldaps://ldap-server.acme.internal&lt;/span&gt;
&lt;span class="py"&gt;ldap_auth_dn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;cn=sssd,ou=service-accounts,dc=acme,dc=internal&lt;/span&gt;
&lt;span class="py"&gt;ldap_auth_dn_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;SSSDPass123&lt;/span&gt;
&lt;span class="py"&gt;ldap_base&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ou=users,dc=acme,dc=internal&lt;/span&gt;

&lt;span class="err"&gt;passdb&lt;/span&gt; &lt;span class="err"&gt;ldap&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;ldap_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=posixAccount)(uid=%{user}))&lt;/span&gt;
  &lt;span class="py"&gt;ldap_bind&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;userdb&lt;/span&gt; &lt;span class="err"&gt;ldap&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;ldap_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(&amp;amp;(objectClass=posixAccount)(uid=%{user}))&lt;/span&gt;
  &lt;span class="err"&gt;fields&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;uid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;vmail&lt;/span&gt;
    &lt;span class="py"&gt;gid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;vmail&lt;/span&gt;
    &lt;span class="py"&gt;home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/mail/vhosts/%{user | username}&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reuses the same read-only service account from the LDAP lab. &lt;code&gt;ldap_bind = yes&lt;/code&gt; means Dovecot verifies passwords by actually binding to LDAP as the user rather than fetching and comparing the hash. The &lt;code&gt;userdb&lt;/code&gt; block maps all mail storage to a dedicated &lt;code&gt;vmail&lt;/code&gt; system user instead of running as the LDAP user's UID.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;ldap_auth_dn_password&lt;/code&gt; is stored in plaintext, but &lt;code&gt;/etc/dovecot/conf.d/auth-ldap.conf.ext&lt;/code&gt; is owned by root and not readable by other users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Dovecot Mail Storage and Postfix Integration
&lt;/h3&gt;

&lt;p&gt;Since LDAP users don't have home directories on the mail server, I created a dedicated &lt;code&gt;vmail&lt;/code&gt; system user to own all mailboxes and set up a central mail storage directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; vmail
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/mail/vhosts
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; vmail:vmail /var/mail/vhosts
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;770 /var/mail/vhosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I updated &lt;code&gt;/etc/dovecot/conf.d/10-mail.conf&lt;/code&gt; to use this path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;mail_driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;maildir&lt;/span&gt;
&lt;span class="py"&gt;mail_home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/mail/vhosts/%{user | username}&lt;/span&gt;
&lt;span class="py"&gt;mail_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;%{home}/Maildir&lt;/span&gt;
&lt;span class="py"&gt;first_valid_uid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I configured the LMTP and auth unix sockets in &lt;code&gt;/etc/dovecot/conf.d/10-master.conf&lt;/code&gt; so Postfix can hand off incoming mail to Dovecot and use Dovecot for SMTP authentication.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;service lmtp&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;service&lt;/span&gt; &lt;span class="err"&gt;lmtp&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;unix_listener&lt;/span&gt; &lt;span class="err"&gt;/var/spool/postfix/private/dovecot-lmtp&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0600&lt;/span&gt;
    &lt;span class="py"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;postfix&lt;/span&gt;
    &lt;span class="py"&gt;group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;postfix&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;service auth&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;service&lt;/span&gt; &lt;span class="err"&gt;auth&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;unix_listener&lt;/span&gt; &lt;span class="err"&gt;/var/spool/postfix/private/auth&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0660&lt;/span&gt;
    &lt;span class="py"&gt;user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;postfix&lt;/span&gt;
    &lt;span class="py"&gt;group&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;postfix&lt;/span&gt;
  &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restarted Dovecot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart dovecot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sending mail
&lt;/h3&gt;

&lt;p&gt;I sent a test mail from &lt;code&gt;jsmith&lt;/code&gt; to &lt;code&gt;adoe&lt;/code&gt; over SMTP from a desktop VM on the same network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;debian@debian:~/acme-certs&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"smtp://mail-server.acme.internal:25"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mail-from&lt;/span&gt; &lt;span class="s2"&gt;"jsmith@acme.internal"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mail-rcpt&lt;/span&gt; &lt;span class="s2"&gt;"adoe@acme.internal"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--upload-file&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
From: jsmith@acme.internal
To: adoe@acme.internal
Subject: Test from jsmith

Hello Alice, this is John.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postfix accepted it and delivered it to Dovecot via LMTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;250 2.0.0 Ok: queued as 98B624018B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verified the mail landed in adoe's mailbox on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;debian@mail-server:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo ls&lt;/span&gt; /var/mail/vhosts/adoe/Maildir/new/
1773903163.M693787P7845.mail-server.acme.internal,S&lt;span class="o"&gt;=&lt;/span&gt;560,W&lt;span class="o"&gt;=&lt;/span&gt;576
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving mail over IMAPS
&lt;/h3&gt;

&lt;p&gt;I verified that adoe can authenticate and retrieve mail over IMAPS using their LDAP credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;debian@debian:~/acme-certs&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; &lt;span class="s2"&gt;"imaps://mail-server.acme.internal/INBOX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="s2"&gt;"adoe:Password456"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;* SSL certificate verify ok.
* Connected to mail-server.acme.internal (192.168.57.7) port 993
&amp;lt; A002 OK Logged in
&amp;lt; * LIST (\HasNoChildren) "." INBOX
&amp;lt; A003 OK List completed (0.001 + 0.000 secs).
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TLS handshake succeeded using the mkcert CA, LDAP authentication worked, and the INBOX is accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thunderbird Verification
&lt;/h2&gt;

&lt;p&gt;Thunderbird has its own certificate store separate from the system trust store, so I had to manually import &lt;code&gt;rootCA.pem&lt;/code&gt; into Thunderbird settings before it would trust the IMAPS connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  adoe sends an email to jsmith
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgul53pqgp6lark4z3vzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgul53pqgp6lark4z3vzx.png" alt="001" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  jsmith opens it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gxqwxkkahcpp8ukt4k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1gxqwxkkahcpp8ukt4k5.png" alt="002" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  jsmith sends a reply email to adoe
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimyam4kjoec5ivbx9hux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimyam4kjoec5ivbx9hux.png" alt="003" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  adoe views the reply email
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmhdz9aw82uk9744hpim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmhdz9aw82uk9744hpim.png" alt="004" width="799" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Postfix and Dovecot are now running as a mail server for &lt;code&gt;acme.internal&lt;/code&gt;. Users defined in the OpenLDAP directory can send and receive mail using their LDAP credentials. Postfix looks up valid recipients in LDAP so it accepts mail for LDAP users. Dovecot authenticates over LDAPS using the same read-only service account from the LDAP lab and stores mail in a central directory owned by a dedicated &lt;code&gt;vmail&lt;/code&gt; user. IMAP access is over TLS only.&lt;/p&gt;

&lt;p&gt;This mail server will be used in the next part of this series as the SMTP backend for Keycloak notifications.&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>linux</category>
      <category>cybersecurity</category>
      <category>mailserver</category>
    </item>
  </channel>
</rss>
