<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Network on HabibiOps</title><link>https://habibiops.com/categories/network/</link><description>Recent content in Network on HabibiOps</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 30 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://habibiops.com/categories/network/index.xml" rel="self" type="application/rss+xml"/><item><title>AWS Systems Manager Session Manager Port Forwarding</title><link>https://habibiops.com/p/aws-systems-manager-session-manage-port-forwarding/</link><pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate><guid>https://habibiops.com/p/aws-systems-manager-session-manage-port-forwarding/</guid><description>&lt;img src="https://habibiops.com/p/aws-systems-manager-session-manage-port-forwarding/assets/ssm-cover.drawio.svg" alt="Featured image of post AWS Systems Manager Session Manager Port Forwarding" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;In the &lt;a class="link" href="https://habibiops.com/p/aws-client-vpn-endpoint-setup/" target="_blank" rel="noopener"
&gt;previous post&lt;/a&gt;, we discussed how to use the AWS VPN
client to connect to private subnets in a VPC. We used Entra ID to perform federated user-based authentication. This
technique is useful for enterprises using federated authentication, but there is another way to achieve the same result
using AWS Systems Manager Session Manager (SSM) and port forwarding.&lt;/p&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://habibiops.com/p/aws-systems-manager-session-manage-port-forwarding/assets/ssm.svg"
loading="lazy"
alt="SSM Port Forwarding to DB"
&gt;&lt;/p&gt;
&lt;p&gt;The diagram above shows the main components used for the SSM port forwarding session setup. The diagram shows the
following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The bastion host EC2 instance that is located in the private subnet and that is used to initiate the port forwarding
session with the attached IAM role&lt;/li&gt;
&lt;li&gt;The EC2 instance security group that allows inbound traffic only from the VPC CIDR range&lt;/li&gt;
&lt;li&gt;The RDS instance in the private subnet that will be used as the target for the port forwarding session&lt;/li&gt;
&lt;li&gt;The RDS security group that allows inbound traffic on the DB port (5432) only from the bastion host EC2 instance&lt;/li&gt;
&lt;li&gt;The SSM session manager document that will be used to initiate the port forwarding session&lt;/li&gt;
&lt;li&gt;The NAT gateway that will be used to route traffic from the private subnet to the internet&lt;/li&gt;
&lt;li&gt;The development local machine that will be used to connect to the RDS instance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="rds-db-connection-with-ssm-port-forwarding"&gt;RDS DB Connection with SSM Port Forwarding
&lt;/h2&gt;&lt;p&gt;The following parameters are required for the port forwarding session:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS Region (e.g. eu-central-1)&lt;/li&gt;
&lt;li&gt;EC2 instance id (e.g. i-04e29d6b82ca52fbc)&lt;/li&gt;
&lt;li&gt;RDS host (e.g. mytestdb-instance-1.clzjs8sy98st.eu-central-1.rds.amazonaws.com)&lt;/li&gt;
&lt;li&gt;RDS port (e.g. 5432)&lt;/li&gt;
&lt;li&gt;RDS username (e.g. postgres)&lt;/li&gt;
&lt;li&gt;RDS password (e.g. arn:aws:secretsmanager:eu-central-1:123456789012:secret:rds!)&lt;/li&gt;
&lt;li&gt;RDS cluster identifier (e.g. cluster-506ac9d4-c6f0-5421-911f-85dec405f14a-A12ncL)&lt;/li&gt;
&lt;li&gt;RDS DB name (e.g. mytestdb)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install
the &lt;a class="link" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" target="_blank" rel="noopener"
&gt;session manager aws cli plugin&lt;/a&gt;
locally and start a new session. Keep the session running and execute further commands from a new terminal session:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ aws ssm start-session --target i-04e29d6b82ca52fbc --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters &lt;span class="s1"&gt;&amp;#39;{&amp;#34;portNumber&amp;#34;:[&amp;#34;5432&amp;#34;],&amp;#34;localPortNumber&amp;#34;:[&amp;#34;1053&amp;#34;],&amp;#34;host&amp;#34;:[&amp;#34;mytestdb-instance-1.clzjs8sy98st.eu-central-1.rds.amazonaws.com&amp;#34;]}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Starting session with SessionId: 429fec6e-29cc-422b-892f-9b8a6973c131-a5xit52zpidhlt7bb95rglflzy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Port &lt;span class="m"&gt;1053&lt;/span&gt; opened &lt;span class="k"&gt;for&lt;/span&gt; sessionId 429fec6e-29cc-422b-892f-9b8a6973c131-a5xit52zpidhlt7bb95rglflzy.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Waiting &lt;span class="k"&gt;for&lt;/span&gt; connections...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Export the correct AWS region and RDS parameters in a new terminal session to connect from a local client:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;eu-central-1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RDS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RDS_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;5432&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RDS_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;RDS_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mytestdb&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PGPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws secretsmanager get-secret-value --secret-id &lt;span class="s1"&gt;&amp;#39;arn:aws:secretsmanager:eu-central-1:123456789012:secret:rds!cluster-506ac9d4-c6f0-5421-911f-85dec405f14a-A12ncL&amp;#39;&lt;/span&gt; --query SecretString --output text &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.password&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;PGPASSWORD&lt;/code&gt; environment variable is automatically picked up by &lt;code&gt;psql&lt;/code&gt; when connecting to a PostgreSQL server.
Connect using &lt;code&gt;psql&lt;/code&gt; to &lt;code&gt;localhost&lt;/code&gt; and port &lt;code&gt;5432&lt;/code&gt; that match the forwarded session that was initiated:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ psql -h &lt;span class="nv"&gt;$RDS_HOST&lt;/span&gt; -p &lt;span class="nv"&gt;$RDS_PORT&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dbname=&lt;/span&gt;&lt;span class="nv"&gt;$RDS_DB&lt;/span&gt;&lt;span class="s2"&gt; user=&lt;/span&gt;&lt;span class="nv"&gt;$RDS_USERNAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql-18 &lt;span class="o"&gt;(&lt;/span&gt;18.3 &lt;span class="o"&gt;(&lt;/span&gt;Homebrew&lt;span class="o"&gt;)&lt;/span&gt;, server 17.7&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SSL connection &lt;span class="o"&gt;(&lt;/span&gt;protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: postgresql&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Type &lt;span class="s2"&gt;&amp;#34;help&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; help.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;mytestdb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt; &lt;span class="se"&gt;\d&lt;/span&gt;t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; List of tables
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Schema &lt;span class="p"&gt;|&lt;/span&gt; Name &lt;span class="p"&gt;|&lt;/span&gt; Type &lt;span class="p"&gt;|&lt;/span&gt; Owner
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--------+-------------+-------+----------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public &lt;span class="p"&gt;|&lt;/span&gt; dummy_table &lt;span class="p"&gt;|&lt;/span&gt; table &lt;span class="p"&gt;|&lt;/span&gt; postgres
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; row&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;mytestdb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The connection was established, and the describe table command shows the proper output.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;This post demonstrates how to use AWS Systems Manager Session Manager to establish a port forwarding session to a remote
DB host. This is useful for scenarios where you need to connect to a remote DB instance from a local development machine
for debugging or testing purposes. No ssh connection or public internet-facing endpoints are required for this scenario
which makes it a practical solution with minimal overhead.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://aws.amazon.com/blogs/mt/use-port-forwarding-in-aws-systems-manager-session-manager-to-connect-to-remote-hosts/" target="_blank" rel="noopener"
&gt;https://aws.amazon.com/blogs/mt/use-port-forwarding-in-aws-systems-manager-session-manager-to-connect-to-remote-hosts/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AWS Client VPN Endpoint Setup</title><link>https://habibiops.com/p/aws-client-vpn-endpoint-setup/</link><pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate><guid>https://habibiops.com/p/aws-client-vpn-endpoint-setup/</guid><description>&lt;img src="https://habibiops.com/p/aws-client-vpn-endpoint-setup/assets/ec2vpn-cover.drawio.svg" alt="Featured image of post AWS Client VPN Endpoint Setup" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Some enterprise customers do not allow SSH access to their AWS EC2 instances for security reasons. However, they still
need to access their EC2 instances from their corporate network or other remote locations for debugging purposes.
Setting up a bastion host inside a public subnet is unfortunately not an option in this case.&lt;/p&gt;
&lt;p&gt;One approach is using AWS Systems Manager (SSM) Session Manager to establish a secure connection between the EC2
instance and the developer&amp;rsquo;s client. Another approach is using AWS Client VPN to establish a VPN tunnel between the
private subnets where the instance is running and any developer machine. One advantage of using AWS Client VPN is that
it allows you to control access to the instance based on the identity of the developer using Entra ID for
authentication, for example - including MFA. Another advantage is the target network association on the subnet level
with and the authorization rules that would drop any traffic that does not match those predefined firewall rules.&lt;/p&gt;
&lt;p&gt;In this post, we will see how to set up an AWS Client VPN endpoint using Terraform and how
to manage access using Entra ID. We will also demonstrate how to use an internal bastion host that is located inside the
private subnets without having a public IP address as a secure gateway to access other internal EC2 instances.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;We will be using Entra ID for authentication and user management. The first step would be to create a new enterprise
application inside the Azure Portal Microsoft Entra admin center.&lt;/p&gt;
&lt;h3 id="iam-saml-identity-provider"&gt;IAM SAML Identity Provider
&lt;/h3&gt;&lt;p&gt;The details are described in an official Microsoft
Entra ID article &lt;a class="link" href="https://learn.microsoft.com/en-us/entra/identity/saas-apps/aws-clientvpn-tutorial" target="_blank" rel="noopener"
&gt;here&lt;/a&gt;. Here is a
high-level summary of the necessary steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New enterprise application (AWS ClientVPN)&lt;/li&gt;
&lt;li&gt;Setup Single Sing-On (SSO) with SAML
&lt;ul&gt;
&lt;li&gt;Identifier (Entity ID): &lt;code&gt;urn:amazon:webservices:clientvpn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reply URL (Assertion Consumer Service URL): &lt;code&gt;http://127.0.0.1:35001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Attributes &amp;amp; Claims:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FirstName&lt;/code&gt;: &lt;code&gt;user.givenname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LastName&lt;/code&gt;: &lt;code&gt;user.surname&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memberOf&lt;/code&gt;: &lt;code&gt;user.groups&lt;/code&gt; (important for using authorization rules using groups and their ids)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Unique User Identifier&lt;/code&gt;: &lt;code&gt;user.userprincipalname&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SAML Signing Certificate / Signing Options: Sign SAML response and assertion (otherwise authentication will be
rejected by AWS)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWS mentions how to set up federated
authentication &lt;a class="link" href="https://aws.amazon.com/blogs/apn/how-to-integrate-aws-client-vpn-with-azure-active-directory/" target="_blank" rel="noopener"
&gt;here&lt;/a&gt;
including the details needed for the user attributes and claims. After creating the enterprise application, you will
need to download Federation Metadata XML file and then create a new identity provider in the AWS IAM section of the AWS
console. The downloaded metadata XML file should be uploaded to the newly created identity provider.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AWS only allows one SAML identity provider per Client VPN endpoint, and Entra ID only allows one enterprise
application with the same Unique User Identifier per Azure tenant. This means you cannot create multiple SAML
identity providers in the same Azure tenant if you have multiple AWS Client VPN endpoints. If multiple AWS Client VPN
endpoints are planned, then you will need to create multiple Entra ID groups in the same enterprise application and
use the group IDs in the authorization rules to have separate access to the different private subnets. More on this
topic in the Authorization Rules section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="server-certificate"&gt;Server Certificate
&lt;/h3&gt;&lt;p&gt;The &amp;ldquo;Authentication Info&amp;rdquo; section requires a &amp;ldquo;Server certificate ARN&amp;rdquo; when creating the client VPN endpoint. The
certificate could be a self-signed certificate for our use case since we will not be using certificate-based
authentication but federated user-based authentication. Make sure the following required X509v3 extensions are present
in the server certificate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;X509v3 Extended Key Usage: TLS Web Server Authentication&lt;/li&gt;
&lt;li&gt;X509v3 Key Usage: Digital Signature, Key Encipherment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example of certificate generation using &lt;a class="link" href="https://github.com/OpenVPN/easy-rsa" target="_blank" rel="noopener"
&gt;easy-rsa&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/OpenVPN/easy-rsa.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; easy-rsa/easyrsa3/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./easyrsa init-pki
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create a new CA&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./easyrsa build-ca nopass
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create a new server certificate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./easyrsa build-server-full ec2vpn.myawesomecompany.internal nopass
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# will not be needed for this case but is mentioned here for completeness &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./easyrsa build-client-full ec2vpnclient1.myawesomecompany.internal nopass
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This can now be added inside AWS Certificate Manager (ACM), and the ARN can be used as the Server certificate ARN.&lt;/p&gt;
&lt;h2 id="client-vpn-endpoint-setup"&gt;Client VPN Endpoint Setup
&lt;/h2&gt;&lt;p&gt;Create a client VPN endpoint with the following main settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Association type: VPC and then choose the VPC ID.&lt;/li&gt;
&lt;li&gt;client IPv4 CIDR block (The address range cannot overlap with the target network address range, the VPC address range,
or any of the routes that will be associated with the client VPN endpoint): For example, use 172.20.0.0/22 if your VPC
CIDR block is 10.0.0.0/16.&lt;/li&gt;
&lt;li&gt;Authentication / Server certificate ARN: previously created server certificate ARN.&lt;/li&gt;
&lt;li&gt;Authentication / Use user-based authentication / Federated authentication / SAML provider ARN: previously created
inside the IAM identity provider.&lt;/li&gt;
&lt;li&gt;Connection protocol / Tunnel mode: Full-tunnel if all client traffic should be routed through the VPN tunnel or use
Split-tunnel if only traffic matching client VPN endpoint routes should be routed through the VPN tunnel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="target-network-associations"&gt;Target Network Associations
&lt;/h3&gt;&lt;p&gt;After creating the client VPN endpoint, you will need to associate it with one or more subnets that should be accessible
from the client VPN endpoint. There is one limitation here, however: &lt;strong&gt;only one subnet association per availability zone
is allowed per client VPN endpoint&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you have multiple private subnets in the same availability zone, or separate private and database subnets in the same
availability zone, for example, then you need to use a private subnet as a central bastion host to reach the other
subnets or to connect to the database.&lt;/p&gt;
&lt;h3 id="authorization-rules"&gt;Authorization Rules
&lt;/h3&gt;&lt;p&gt;Authorization rules act as firewall rules to grant specific clients access to the specified network. You should have an
authorization rule for each network you want to grant access to. Since we have set up Entra ID for authentication and
added the SAML identity provider, we can use the Entra ID group IDs as the access group IDs in the authorization rules.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s add an authorization rule to access the private subnet that hosts our EC2 instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Destination network to enable access: &lt;code&gt;10.0.2.0/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Grant access to: Allow access to users in a specific access group
&lt;ul&gt;
&lt;li&gt;Access group ID: &lt;code&gt;07fc77bc-fa54-4b61-931e-2be27857671b&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only users in the specified access group will be able to access the private subnet. All other traffic coming from other
users connected to the client VPN endpoint will be dropped. This way, we can control access to the private subnets
based on the identity of the user inside Entra ID.&lt;/p&gt;
&lt;h3 id="route-table"&gt;Route Table
&lt;/h3&gt;&lt;p&gt;Depending on whether you are using full-tunnel or split-tunnel, you will need to add an extra route to enable internet
access on your local machine while connected to the client VPN endpoint. If you are using split-tunnel, then you can
skip this step. If you are using full-tunnel, then you need to add an extra route where the destination is &lt;code&gt;0.0.0.0/0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create route:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Route destination: &lt;code&gt;0.0.0.0/0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Subnet ID for target network association: select any associated subnet that has outgoing internet connectivity using a
NAT gateway&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="architecture-diagram"&gt;Architecture Diagram
&lt;/h2&gt;&lt;p&gt;We have explained all the steps above in detail. Here is a high-level architecture diagram of the proposed solution:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://habibiops.com/p/aws-client-vpn-endpoint-setup/assets/ec2vpn.drawio.svg"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VPN users are managed using Entra ID: users in the allowed access group will be able to access the private subnets.&lt;/li&gt;
&lt;li&gt;there is one bastion host inside the private subnets that can be reached when connected using the AWS Client VPN.&lt;/li&gt;
&lt;li&gt;the private subnets have outgoing internet connectivity using a NAT gateway.&lt;/li&gt;
&lt;li&gt;the bastion host can reach other internal EC2 instances such as the backend VM or the DB using the private IP address
as long as the security group allows it.&lt;/li&gt;
&lt;li&gt;EC2 instances in the public subnet such as the load balancer VM do not need to be accessible from the internet via
ssh.&lt;/li&gt;
&lt;li&gt;No ssh port is open to the public internet, and all traffic is routed through the VPN tunnel and then internally via
the bastion host (jumphost).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="automation-with-terraform"&gt;Automation with Terraform
&lt;/h2&gt;&lt;p&gt;The GitHub repository for this blog post is
available &lt;a class="link" href="https://github.com/habibiops/aws-client-vpn-endpoint-setup" target="_blank" rel="noopener"
&gt;here&lt;/a&gt;. We will go over the main resources that
were used in the module. The &lt;code&gt;aws_ec2_client_vpn_endpoint&lt;/code&gt; resource is the main resource that we will use to create the
client VPN endpoint. The network associations, authorization rules, and routes are created using the following
resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aws_ec2_client_vpn_network_association&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aws_ec2_client_vpn_authorization_rule&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aws_ec2_client_vpn_route&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a class="link" href="https://github.com/habibiops/aws-client-vpn-endpoint-setup/tree/main/example" target="_blank" rel="noopener"
&gt;examples folder&lt;/a&gt; contains an example
of a new vpc with a new client VPN endpoint. The VPC contains three private subnets that are associated with the client
VPN endpoint. The user has to provide a public ssh key, the server certificate ARN, and the SAML identity provider ARN.
The Entra ID group ID is also used inside the authorization rules section.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;In this post, we have seen how to set up an AWS Client VPN endpoint and how to manage access using Entra ID. Federated
user-based authentication is an important requirement in most enterprises for compliance reasons. The solution to use an
internal bastion host that is located inside the private subnets without having a public IP address that can be reached
from the client VPN endpoint was discussed. From that point on, using the bastion host as a secure gateway to access
other internal EC2 instances meets the requirements of most organizations.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://learn.microsoft.com/en-us/entra/identity/saas-apps/aws-clientvpn-tutorial" target="_blank" rel="noopener"
&gt;Configure AWS ClientVPN for Single sign-on with Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/federated-authentication.html" target="_blank" rel="noopener"
&gt;Single sign-on — SAML 2.0-based federated authentication — in Client VPN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://aws.amazon.com/blogs/apn/how-to-integrate-aws-client-vpn-with-azure-active-directory/" target="_blank" rel="noopener"
&gt;How to Integrate AWS Client VPN with Azure Active Directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/OpenVPN/easy-rsa" target="_blank" rel="noopener"
&gt;easy-rsa - Simple shell based CA utility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-working-rules.html#auth-rule-example-scenarios" target="_blank" rel="noopener"
&gt;AWS Client VPN authorization rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cloudposse/terraform-aws-ec2-client-vpn" target="_blank" rel="noopener"
&gt;cloudposse / terraform-aws-ec2-client-vpn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/@lyndon_56890/the-incredibly-pedantic-aws-client-vpn-documentation-352cecb23194" target="_blank" rel="noopener"
&gt;The Incredibly Pedantic AWS Client VPN Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>