<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Fri, 22 Nov 2024 16:36:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>FreeBSD: OpenVPN server in a LAN (local network) environment</title>
		<link>https://blog.andreev.it/2024/11/freebsd-openvpn-server-in-a-lan-local-network-environment/</link>
					<comments>https://blog.andreev.it/2024/11/freebsd-openvpn-server-in-a-lan-local-network-environment/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Fri, 22 Nov 2024 16:36:12 +0000</pubDate>
				<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[freebsd]]></category>
		<category><![CDATA[LAN]]></category>
		<category><![CDATA[OpenVPN]]></category>
		<category><![CDATA[pf]]></category>
		<category><![CDATA[VPN]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=10074</guid>

					<description><![CDATA[In this post I&#8217;ll describe a use case that I am currently using. I&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll describe a use case that I am currently using. I have a standard Internet connection that goes to my Eero mesh router and behind that I have a homelab on 192.168.1.0/24 network as shown in the diagram. I have a port forwarding configured on the router so I can access one of my Linux servers, but if I want to access other servers, I have to either port forward SSH on some other-than-22/tcp port or use the Linux server as a jump-box. None of this is good, so I decided to create an OpenVPN server. Anytime I VPN to this server I can ssh/rdp to any of the internal servers. The OpenVPN server requires one nic.<br />
<strong>NOTE</strong>: This post is very FreeBSD specific so if you want to use some Linux server as OpenVPN server, this guide won&#8217;t help you at all.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/11/P177-01.png" data-rel="penci-gallery-image-content" ><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/11/P177-01.png" alt="" width="721" height="491" class="aligncenter size-full wp-image-10078" srcset="https://blog.andreev.it/wp-content/uploads/2024/11/P177-01.png 721w, https://blog.andreev.it/wp-content/uploads/2024/11/P177-01-300x204.png 300w, https://blog.andreev.it/wp-content/uploads/2024/11/P177-01-585x398.png 585w" sizes="(max-width: 721px) 100vw, 721px" /></a><br />
<strong>IMPORTANT</strong>: On your home router port-forward port 1194/udp to your OpenVPN server.</p>
<h1>OpenVPN server</h1>
<p>Let&#8217;s install OpenVPN server first.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install openvpn
</pre>
<p>Make sure it starts on boot and enable the NAT.</p>
<pre class="brush: bash; title: ; notranslate">
sysrc openvpn_enable=YES
sysrc gateway_enable=YES
</pre>
<p>Next, we need a directory where we&#8217;ll store our config files and the PKI.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /usr/local/etc/openvpn
cd /usr/local/etc/openvpn
</pre>
<p>Create the PKI structure.</p>
<pre class="brush: bash; title: ; notranslate">
easyrsa init-pki
</pre>
<p>Go to the newly created <em>pki </em>directory and make a copy of the <em>vars.example</em> file.</p>
<pre class="brush: bash; title: ; notranslate">
cd pki
cp vars.example vars
</pre>
<p>Now edit this file and uncomment the following lines and change to match your needs. It&#8217;s optional and you can keep everything-as-is.</p>
<pre class="brush: bash; title: ; notranslate">
set_var EASYRSA_REQ_COUNTRY     &quot;US&quot;
set_var EASYRSA_REQ_PROVINCE    &quot;NJ&quot;
set_var EASYRSA_REQ_CITY        &quot;Allentown&quot;
set_var EASYRSA_REQ_ORG         &quot;iAndreev&quot;
set_var EASYRSA_REQ_EMAIL       &quot;mail@somemail.com&quot;
set_var EASYRSA_REQ_OU          &quot;iAndreev&quot;
set_var EASYRSA_CA_EXPIRE       3650
set_var EASYRSA_CERT_EXPIRE     825
</pre>
<p>Now create the Certificate Authority based on these values.</p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/local/etc/openvpn
easyrsa build-ca
</pre>
<p>This command will prompt you to create a password that you will you to sign the certificates for the server and the clients.<br />
Then create the DH parameters. It might take 30+ seconds.</p>
<pre class="brush: bash; title: ; notranslate">
easyrsa gen-dh
</pre>
<p>Create the server request and sign it.</p>
<pre class="brush: bash; title: ; notranslate">
easyrsa build-server-full server nopass
</pre>
<p>You have to type <em>yes </em>to confirm and then use the password that you created above to sign the cert.<br />
The command will create some files under the <em>pki </em>directory and we&#8217;ll need those later.<br />
Next, we&#8217;ll create the same for the client. In my case it&#8217;s a Dell laptop, so I use the word dell, but you can use client, client1, something-whatever&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
easyrsa build-client-full dell nopass
</pre>
<p>Same thing, answer <em>yes </em>and then use the password to sign the certs.<br />
Copy the OpenVPN config example file that comes with the package.</p>
<pre class="brush: bash; title: ; notranslate">
cp /usr/local/share/examples/openvpn/sample-config-files/server.conf /usr/local/etc/openvpn/openvpn.conf
</pre>
<p>Edit<em> openvpn.conf</em> and make sure these values are changed and match the certs you created earlier.</p>
<pre class="brush: bash; title: ; notranslate">
ca /usr/local/etc/openvpn/pki/ca.crt
cert /usr/local/etc/openvpn/pki/issued/server.crt
key /usr/local/etc/openvpn/pki/private/server.key

dh /usr/local/etc/openvpn/pki/dh.pem
</pre>
<p>In addition to that, find the line that says push route and create a new one that matches your internal subnet. In my case it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
push &quot;route 192.168.1.0 255.255.255.0&quot;
</pre>
<p>If you have internal DNS and you want to send that as well, uncomment the following line and change to match your DNS IP.</p>
<pre class="brush: bash; title: ; notranslate">
push &quot;dhcp-option DNS 208.67.222.222&quot;
</pre>
<p>Now, you can start the OpenVPN server. Make sure you check the syslog for any errors.</p>
<pre class="brush: bash; title: ; notranslate">
service openvpn start
tail /var/log/messages
</pre>
<h1>OpenVPN client</h1>
<p>On your laptop that you want to use as a VPN client, download the OpenVPN client from <a href="https://openvpn.net/community-downloads/" target="_blank">here</a>. When you install the client, you&#8217;ll have a generic config file under <em>C:\Program Files\OpenVPN\sample-config</em> folder. Copy that file under <em>c:\users\[your_username]\OpenVPN\config</em>.<br />
Then, edit the file and change the following lines.</p>
<pre class="brush: bash; title: ; notranslate">
remote public-ip-or-hostname-of-your-router 1194
ca ca.crt
cert dell.crt
key dell.key
</pre>
<p>NOTE: Make sure you copy <em>ca.crt</em> from <em>/usr/local/etc/openvpn/pki</em>, <em>dell.crt</em> or client.crt or whatever.crt from <em>/usr/local/etc/openvpn/pki/issued</em> and<em> dell.key</em> or client.key or whatever.key from <em>/usr/local/etc/openvpn/pki/private</em> directories to the same <em>c:\users\[your-user-name]\OpenVPN\config</em> folder. There should be 4 files there. The config file, the CA certificate, the client certificate and the private key for the client.<br />
Start the OpenVPN client and connect. You shouldn&#8217;t have any problems and you&#8217;ll get 10.8.0.2 IP assigned to your laptop. If you try to ping 10.8.0.1 you&#8217;ll get a response. Do route print and you&#8217;ll see that the routes for 192.168.1.0 are going over 10.8.0.1. But, if you try to ping some server internally, e.g. 192.168.1.21, you won&#8217;t get any response. That&#8217;s because that server doesn&#8217;t know the route back. You can easily add the route to each server but it&#8217;s not practical. Let&#8217;s make one more change to the server.</p>
<h1>OpenVPN server</h1>
<p>Add these two lines to <em>/etc/rc.conf</em>. That&#8217;s the FreeBSD pf firewall.</p>
<pre class="brush: bash; title: ; notranslate">
pf_enable=&quot;YES&quot;
pf_rules=&quot;/etc/pf.conf&quot;
</pre>
<p>Now, create this config file in <em>/etc/pf.conf</em>. Many thanks to <a href="https://gundersen.net/openvpn-server-on-freebsd-with-pf-firewall/" target="_blank">this </a>guy. </p>
<pre class="brush: bash; title: ; notranslate">
vpnclients = &quot;10.8.0.0/24&quot;
# The name of your NIC. I am using a VM on ESXi and vmx0 is my NIC.
wanint = &quot;vmx0&quot;
# put your tunnel interface here, it is usually tun0
vpnint = &quot;tun0&quot;
# OpenVPN by default runs on udp port 1194
udpopen = &quot;{1194}&quot;
# Open the SSH port.
tcpopen = &quot;{22}&quot;
icmptypes = &quot;{echoreq, unreach}&quot;

set skip on lo
# the essential line
nat on $wanint inet from $vpnclients to any -&gt; $wanint

block in
pass in on $wanint proto udp from any to $wanint port $udpopen
pass in on $wanint proto tcp from any to $wanint port $tcpopen
# the following two lines could be made stricter if you don&#039;t trust the clients
pass out quick
pass in on $vpnint from any to any
pass in inet proto icmp all icmp-type $icmptypes
</pre>
<p>Reboot the OpenVPN server and if you reconnect from the client, you can use the internal IPs directly to connect.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/11/freebsd-openvpn-server-in-a-lan-local-network-environment/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS, Azure: Site-to-site (S2S) VPN tunnel between AWS and Azure</title>
		<link>https://blog.andreev.it/2024/10/aws-azure-site-to-site-s2s-vpn-tunnel-between-aws-and-azure/</link>
					<comments>https://blog.andreev.it/2024/10/aws-azure-site-to-site-s2s-vpn-tunnel-between-aws-and-azure/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 28 Oct 2024 14:36:49 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[BGP]]></category>
		<category><![CDATA[IKE]]></category>
		<category><![CDATA[S2S]]></category>
		<category><![CDATA[Site-to-Site]]></category>
		<category><![CDATA[VPN]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=10004</guid>

					<description><![CDATA[In this post I&#8217;ll create a site-to-site (S2S) tunnel between AWS and Azure. It&#8217;s&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll create a site-to-site (S2S) tunnel between AWS and Azure. It&#8217;s an HA tunnel so it&#8217;s regionally resilient. I&#8217;ll use Terraform for the deployment using a set of modules (download <a href="https://blog.andreev.it/wp-content/uploads/2024/10/modules.tgz">here</a>). The Terraform files will be separated in two different folders. One for Azure and one for AWS.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/10/P175-01.png" data-rel="penci-gallery-image-content" ><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/10/P175-01.png" alt="" width="643" height="581" class="aligncenter size-full wp-image-10006" srcset="https://blog.andreev.it/wp-content/uploads/2024/10/P175-01.png 643w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-01-300x271.png 300w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-01-585x529.png 585w" sizes="(max-width: 643px) 100vw, 643px" /></a><br />
The architectural diagram looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/10/P175-02.png" data-rel="penci-gallery-image-content" ><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/10/P175-02.png" alt="" width="816" height="831" class="aligncenter size-full wp-image-10007" srcset="https://blog.andreev.it/wp-content/uploads/2024/10/P175-02.png 816w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-02-295x300.png 295w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-02-768x782.png 768w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-02-585x596.png 585w" sizes="(max-width: 816px) 100vw, 816px" /></a></p>
<h1>Azure</h1>
<p>For Azure, we will provision a public subnet (10.1.0.0/24) and a Gateway subnet (10.1.1.0/24). Then, we&#8217;ll provision one VNET Gateway with active-active BGP tunnel. The Azure setup requires that we provision 4 local network gateways and S2S connections, but the output will return only 2 public IPs. These are the files.<br />
<strong>provider.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
terraform {
  required_version = &quot;~&gt;1.9.5&quot;
  required_providers {
    azurerm = {
      source  = &quot;hashicorp/azurerm&quot;
      version = &quot;~&gt;3.110.0&quot;
    }
  }
}

provider &quot;azurerm&quot; {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
}
</pre>
<p><strong>azure.tf</strong></p>
<pre class="brush: bash; collapse: true; highlight: [155,196,237,278]; light: false; title: ; toolbar: true; notranslate">
# Create a resource group
module &quot;rg&quot; {
  source = &quot;../../modules/azure/rg-0.0.4&quot;

  az_rg_name     = var.rg_ResourceGroupName
  az_rg_location = var.rgn_RegionLocation
}

# Create a VNET
module &quot;vnet&quot; {
  source = &quot;../../modules/azure/vnet-4.0.0&quot;

  vnet_name           = var.vnet_Name
  resource_group_name = var.rg_ResourceGroupName
  vnet_location       = var.rgn_RegionLocation
  use_for_each        = false
  address_space       = &#x5B;var.vnet_CIDR]
  subnet_prefixes     = &#x5B;var.sub_PublicCIDR, var.sub_GatewayCIDR]
  subnet_names        = &#x5B;var.sub_PublicName, var.sub_GatewayName]

  depends_on = &#x5B;module.rg]
}

# Create a virtual network gateway
module &quot;vnetgw&quot; {
  source = &quot;../../modules/azure/vnetgw-0.5.1&quot;

  location                  = var.rgn_RegionLocation
  name                      = var.vgw_Name
  subnet_address_prefix     = var.sub_GatewayCIDR
  sku                       = var.vgw_SKU
  type                      = var.vgw_Type
  virtual_network_id        = module.vnet.vnet_id
  vpn_generation            = var.vgw_Generation
  vpn_type                  = var.vpn_Type
  subnet_creation_enabled   = false
  vpn_active_active_enabled = true
  vpn_bgp_enabled           = true
  vpn_bgp_settings = {
    asn = var.asn_Azure
  }
  # Create virtual network gateway configurations
  ip_configurations = {
    &quot;ip_config_01&quot; = {
      name            = var.gwc_BGPName1
      apipa_addresses = var.ip_APIPAAddresses1
      public_ip = {
        allocation_method = var.ip_allocation_method
      }
    },
    &quot;ip_config_02&quot; = {
      name            = var.gwc_BGPName2
      apipa_addresses = var.ip_APIPAAddresses2
      public_ip = {
        allocation_method = var.ip_allocation_method
      }
    }
  }
  # Create local network gateways and connections
  local_network_gateways = {
    # AWS Tunnel 1 to Azure Instance 0
    &quot;lngw_01&quot; = {
      name            = var.lng_AWST1A0
      gateway_address = var.ip_AWST1A0
      bgp_settings = {
        asn                 = var.asn_AWS
        bgp_peering_address = var.ip_peering_AWST1A0
      }
      connection = {
        name       = var.conn_AWST1A0
        type       = var.conn_Type
        enable_bgp = true
        shared_key = var.key_SharedKey
        custom_bgp_addresses = {
          primary   = var.ip_primary_AWST1A0
          secondary = var.ip_secondary_AWST1A0
        }
      }
    },
    # AWS Tunnel 2 to Azure Instance 0
    &quot;lngw_02&quot; = {
      name            = var.lng_AWST2A0
      gateway_address = var.ip_AWST2A0
      bgp_settings = {
        asn                 = var.asn_AWS
        bgp_peering_address = var.ip_peering_AWST2A0
      }
      connection = {
        name       = var.conn_AWST2A0
        type       = var.conn_Type
        enable_bgp = true
        shared_key = var.key_SharedKey
        custom_bgp_addresses = {
          primary = var.ip_primary_AWST2A0
        secondary = var.ip_secondary_AWST2A0 }
      }
    },
    # AWS Tunnel 1 to Azure Instance 1
    &quot;lngw_03&quot; = {
      name            = var.lng_AWST1A1
      gateway_address = var.ip_AWST1A1
      bgp_settings = {
        asn                 = var.asn_AWS
        bgp_peering_address = var.ip_peering_AWST1A1
      }
      connection = {
        name       = var.conn_AWST1A1
        type       = var.conn_Type
        enable_bgp = true
        shared_key = var.key_SharedKey
        custom_bgp_addresses = {
          primary = var.ip_primary_AWST1A1
        secondary = var.ip_secondary_AWST1A1 }
      }
    },
    # AWS Tunnel 2 to Azure Instance 1
    &quot;lngw_04&quot; = {
      name            = var.lng_AWST2A1
      gateway_address = var.ip_AWST2A1
      bgp_settings = {
        asn                 = var.asn_AWS
        bgp_peering_address = var.ip_peering_AWST2A1
      }
      connection = {
        name       = var.conn_AWST2A1
        type       = var.conn_Type
        enable_bgp = true
        shared_key = var.key_SharedKey
        custom_bgp_addresses = {
          primary = var.ip_primary_AWST2A1
        secondary = var.ip_secondary_AWST2A1 }
      }
    }
  }

  depends_on = &#x5B;module.vnet]
}
</pre>
<p><strong>variables.tf</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Resource group
variable &quot;rg_ResourceGroupName&quot; {
  type        = string
  default     = &quot;rgVPN&quot;
  description = &quot;Resource group name&quot;
}

variable &quot;rgn_RegionLocation&quot; {
  type        = string
  default     = &quot;eastus2&quot;
  description = &quot;Azure region&quot;
}

# VNET
variable &quot;vnet_Name&quot; {
  type        = string
  default     = &quot;vnetVPN&quot;
  description = &quot;VNET name&quot;
}

variable &quot;vnet_CIDR&quot; {
  type        = string
  default     = &quot;10.1.0.0/16&quot;
  description = &quot;VNET CIDR&quot;
}

variable &quot;sub_PublicCIDR&quot; {
  type        = string
  default     = &quot;10.1.0.0/24&quot;
  description = &quot;CIDR of the public subnet&quot;
}

variable &quot;sub_GatewayCIDR&quot; {
  type        = string
  default     = &quot;10.1.1.0/24&quot;
  description = &quot;CIDR of the Gateway subnet&quot;
}

variable &quot;sub_PublicName&quot; {
  type        = string
  default     = &quot;subPublic&quot;
  description = &quot;Name of the public subnet&quot;
}

variable &quot;sub_GatewayName&quot; {
  type        = string
  default     = &quot;GatewaySubnet&quot;
  description = &quot;Name of the Gateway subnet - DO NOT CHANGE. It has to stay as GatewaySubnet&quot;
}

# Virtual network gateway
variable &quot;vgw_Name&quot; {
  type        = string
  default     = &quot;vgwVPN&quot;
  description = &quot;Name of the virtual gateway&quot;
}

variable &quot;vgw_SKU&quot; {
  type        = string
  default     = &quot;VpnGw2AZ&quot;
  description = &quot;Virtual Gateway SKU&quot;
}

variable &quot;vgw_Type&quot; {
  type        = string
  default     = &quot;Vpn&quot;
  description = &quot;Virtual Gateway Type (VPN or ExpressRoute)&quot;
}

variable &quot;vgw_Generation&quot; {
  type        = string
  default     = &quot;Generation2&quot;
  description = &quot;Virtual Gateway Generation&quot;
}

variable &quot;vpn_Type&quot; {
  type        = string
  default     = &quot;RouteBased&quot;
  description = &quot;VPN type - RouteBased or PolicyBased&quot;
}

variable &quot;asn_Azure&quot; {
  type        = string
  default     = &quot;65000&quot;
  description = &quot;Azure ASN&quot;
}

variable &quot;asn_AWS&quot; {
  type        = string
  default     = &quot;64512&quot;
  description = &quot;AWS ASN&quot;
}

# Virtual network gateway config 1
variable &quot;gwc_BGPName1&quot; {
  type        = string
  default     = &quot;vnetGatewayConfig01&quot;
  description = &quot;Name of the virtual network gateway configuration for the first tunnel&quot;
}

variable &quot;ip_APIPAAddresses1&quot; {
  type        = list(string)
  default     = &#x5B;&quot;169.254.21.2&quot;, &quot;169.254.22.2&quot;]
  description = &quot;APIPA address for the first tunnel&quot;
}

# Virtual network gateway config 2
variable &quot;gwc_BGPName2&quot; {
  type        = string
  default     = &quot;vnetGatewayConfig02&quot;
  description = &quot;Name of the virtual network gateway configuration for the second tunnel&quot;
}

variable &quot;ip_APIPAAddresses2&quot; {
  type        = list(string)
  default     = &#x5B;&quot;169.254.21.6&quot;, &quot;169.254.22.6&quot;]
  description = &quot;APIPA address for the second tunnel&quot;
}

variable &quot;ip_allocation_method&quot; {
  type        = string
  default     = &quot;Static&quot;
  description = &quot;Static or Dynamic public IP&quot;
}

# Local network gateway common settings
variable &quot;conn_Type&quot; {
  type        = string
  default     = &quot;IPsec&quot;
  description = &quot;Connection type: IPsec or Vnet2Vnet&quot;
}

variable &quot;key_SharedKey&quot; {
  type        = string
  default     = &quot;KlimentAndreev1970_&quot;
  description = &quot;Make sure it&#039;s the same as &#039;key_SharedKey&#039; variable in variables.tf under the AWS folder&quot;
  # Don&#039;t go crazy with complexity. AWS side can support only 
  # between 8 and 64 characters in length and cannot start with zero (0). 
  # Allowed characters are alphanumeric characters, periods (.), and underscores (_).
}

# Local network gateway AWS Tunnel 1 to Azure Instance 0
# lngw_01
variable &quot;lng_AWST1A0&quot; {
  type        = string
  default     = &quot;lng_AWSTunnel1ToAzureInstance0&quot;
  description = &quot;From AWS Tunnel 1 to Azure Instance 0&quot;
}

###
### REPLACE FROM THE OUTPUT OF AWS IN STEP 2
###
variable &quot;ip_AWST1A0&quot; {
  type        = string
  default     = &quot;13.58.80.164&quot;
  description = &quot;Enter the output of ip_AWST1IP1 from the AWS output.tf script&quot;
}

variable &quot;ip_peering_AWST1A0&quot; {
  type        = string
  default     = &quot;169.254.21.1&quot;
  description = &quot;BGP Peering IP for AWS Tunnel1 to Azure Instance 0&quot;
}

variable &quot;ip_primary_AWST1A0&quot; {
  type        = string
  default     = &quot;169.254.21.2&quot;
  description = &quot;Primary Custom BGP Address for AWS Tunnel1 to Azure Instance 0&quot;
}

variable &quot;ip_secondary_AWST1A0&quot; {
  type        = string
  default     = &quot;169.254.21.6&quot;
  description = &quot;Secondary Custom BGP Address for AWS Tunnel1 to Azure Instance 0&quot;
}

variable &quot;conn_AWST1A0&quot; {
  type        = string
  default     = &quot;connAWSTunnel1toAzureInstance0&quot;
  description = &quot;Connection name for AWS Tunnel1 to Azure Instance 0&quot;
}

# Local network gateway AWS Tunnel 2 to Azure Instance 0
# lngw_02
variable &quot;lng_AWST2A0&quot; {
  type        = string
  default     = &quot;lng_AWSTunnel2ToAzureInstance0&quot;
  description = &quot;From AWS Tunnel 2 to Azure Instance 0&quot;
}

###
### REPLACE FROM THE OUTPUT OF AWS IN STEP 2
###
variable &quot;ip_AWST2A0&quot; {
  type        = string
  default     = &quot;18.119.84.239&quot;
  description = &quot;Enter the output of ip_AWST2A0 from the AWS output.tf script&quot;
}

variable &quot;ip_peering_AWST2A0&quot; {
  type        = string
  default     = &quot;169.254.22.1&quot;
  description = &quot;BGP Peering IP for AWS Tunnel2 to Azure Instance 0&quot;
}

variable &quot;ip_primary_AWST2A0&quot; {
  type        = string
  default     = &quot;169.254.22.2&quot;
  description = &quot;Primary Custom BGP Address for AWS Tunnel2 to Azure Instance 0&quot;
}

variable &quot;ip_secondary_AWST2A0&quot; {
  type        = string
  default     = &quot;169.254.21.6&quot;
  description = &quot;Secondary Custom BGP Address for AWS Tunnel1 to Azure Instance 1&quot;
}

variable &quot;conn_AWST2A0&quot; {
  type        = string
  default     = &quot;connAWSTunnel2toAzureInstance0&quot;
  description = &quot;Connection name for AWS Tunnel2 to Azure Instance 0&quot;
}

# Local network gateway AWS Tunnel 1 to Azure Instance 1
# lngw_03
variable &quot;lng_AWST1A1&quot; {
  type        = string
  default     = &quot;lng_AWSTunnel1ToAzureInstance1&quot;
  description = &quot;From AWS Tunnel 1 to Azure Instance 1&quot;
}

###
### REPLACE FROM THE OUTPUT OF AWS IN STEP 2
###
variable &quot;ip_AWST1A1&quot; {
  type        = string
  default     = &quot;18.119.2.80&quot;
  description = &quot;Enter the output of ip_AWST1A1 from the AWS output.tf script&quot;
}

variable &quot;ip_peering_AWST1A1&quot; {
  type        = string
  default     = &quot;169.254.21.5&quot;
  description = &quot;BGP Peering IP for AWS Tunnel1 to Azure Instance 1&quot;
}

variable &quot;ip_primary_AWST1A1&quot; {
  type        = string
  default     = &quot;169.254.21.2&quot;
  description = &quot;Primary Custom BGP Address for AWS Tunnel1 to Azure Instance 1&quot;
}

variable &quot;ip_secondary_AWST1A1&quot; {
  type        = string
  default     = &quot;169.254.21.6&quot;
  description = &quot;Secondary Custom BGP Address for AWS Tunnel1 to Azure Instance 1&quot;
}

variable &quot;conn_AWST1A1&quot; {
  type        = string
  default     = &quot;connAWSTunnel1toAzureInstance1&quot;
  description = &quot;Connection name for AWS Tunnel1 to Azure Instance 1&quot;
}

# Local network gateway AWS Tunnel 2 to Azure Instance 1
# lngw_04
variable &quot;lng_AWST2A1&quot; {
  type        = string
  default     = &quot;lng_AWSTunnel2ToAzureInstance1&quot;
  description = &quot;From AWS Tunnel 2 to Azure Instance 1&quot;
}

###
### REPLACE FROM THE OUTPUT OF AWS IN STEP 2
###
variable &quot;ip_AWST2A1&quot; {
  type        = string
  default     = &quot;18.220.73.188&quot;
  description = &quot;Enter the output of ip_AWST2A1 from the AWS ouput.tf script&quot;
}

variable &quot;ip_peering_AWST2A1&quot; {
  type        = string
  default     = &quot;169.254.22.5&quot;
  description = &quot;BGP Peering IP for AWS Tunnel2 to Azure Instance 1&quot;
}

variable &quot;ip_primary_AWST2A1&quot; {
  type        = string
  default     = &quot;169.254.21.2&quot;
  description = &quot;Primary Custom BGP Address for AWS Tunnel2 to Azure Instance 1&quot;
}

variable &quot;ip_secondary_AWST2A1&quot; {
  type        = string
  default     = &quot;169.254.22.6&quot;
  description = &quot;Secondary Custom BGP Address for AWS Tunnel2 to Azure Instance 1&quot;
}

variable &quot;conn_AWST2A1&quot; {
  type        = string
  default     = &quot;connAWSTunnel2toAzureInstance1&quot;
  description = &quot;Connection name for AWS Tunnel2 to Azure Instance 1&quot;
}
</pre>
<p><strong>outputs.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
output &quot;vgw_publicip_1&quot; {
  value       = module.vnetgw.public_ip_addresses.ip_config_01.ip_address
  description = &quot;Use this output to populate the same variable in AWS variables.tf file.&quot;
}

output &quot;vgw_publicip_2&quot; {
  value       = module.vnetgw.public_ip_addresses.ip_config_02.ip_address
  description = &quot;Use this output to populate the same variable in AWS variables.tf file.&quot;
}
</pre>
<p>Once you run the initial <em>terraform apply</em> it will take about 35-40 mins and you&#8217;ll get two IPs as output. You&#8217;ll need these momentarily in the file <strong>variables.tf</strong> under <strong>02.AWS</strong> folder.</p>
<pre class="brush: bash; title: ; notranslate">
vgw_publicip_1 = &quot;48.211.240.129&quot;
vgw_publicip_2 = &quot;48.211.240.186&quot;
</pre>
<h1>AWS</h1>
<p>In AWS we&#8217;ll provision a private and a public subnet as well as two VPN tunnels. This is the Terraform code for that.<br />
<strong>provider.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
terraform {
  required_version = &quot;~&gt;1.9.5&quot;
  required_providers {
    aws = {
      source  = &quot;hashicorp/aws&quot;
      version = &quot;~&gt;5.57.0&quot;
    }
  }
}

provider &quot;aws&quot; {
  region = var.rgn_RegionLocation
}
</pre>
<p><strong>aws.tf</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Create VPC
module &quot;vpc&quot; {

  source = &quot;../../modules/aws/vpc-5.5.1&quot;

  name = var.vpc_Name
  cidr = var.vpc_CIDR

  azs             = var.vpc_AZs
  private_subnets = var.sub_pvtCIDR
  public_subnets  = var.sub_pubCIDR

  enable_nat_gateway = true
  enable_vpn_gateway = true
  amazon_side_asn    = var.asn_AWS
}

# Change the route to enable propagation to public route table
# resource &quot;aws_vpn_gateway_route_propagation&quot; &quot;rtPropg&quot; {
#   vpn_gateway_id = module.vpc.vgw_id
#   route_table_id = module.vpc.public_route_table_ids&#x5B;0]
# }

# Change the route to enable propagation to private route table
resource &quot;aws_vpn_gateway_route_propagation&quot; &quot;rtPvtPropg&quot; {
  vpn_gateway_id = module.vpc.vgw_id
  route_table_id = module.vpc.private_route_table_ids&#x5B;0]
}

# Add the NAT gateway as a route to Internet to the private route table
# resource &quot;aws_route&quot; &quot;r&quot; {
#   route_table_id         = module.vpc.private_route_table_ids&#x5B;0]
#   destination_cidr_block = &quot;0.0.0.0/0&quot;
#   nat_gateway_id         = module.vpc.natgw_ids&#x5B;0]
# }

# Create 2 customer gateways
module &quot;cgw&quot; {
  source = &quot;../../modules/aws/cgw-2.0.1/&quot;

  name = var.cgw_Name

  customer_gateways = {
    IP1 = {
      bgp_asn    = var.asn_Azure
      ip_address = var.vgw_publicip_1
    },
    IP2 = {
      bgp_asn    = var.asn_Azure
      ip_address = var.vgw_publicip_2
    }
  }
}

module &quot;vpngw1&quot; {
  source = &quot;../../modules/aws/vpngw-3.7.2/&quot;

  customer_gateway_id               = module.cgw.ids&#x5B;0]
  vpc_id                            = module.vpc.vpc_id
  vpn_gateway_id                    = module.vpc.vgw_id
  vpn_connection_static_routes_only = false
  local_ipv4_network_cidr           = &quot;0.0.0.0/0&quot;

  tunnel1_inside_cidr   = var.ip_AWST1A0
  tunnel2_inside_cidr   = var.ip_AWST2A0
  tunnel1_preshared_key = var.key_SharedKey
  tunnel2_preshared_key = var.key_SharedKey

  tags = {
    Name = var.vpn_AWSA0Name
  }

  depends_on = &#x5B;module.cgw]

}

module &quot;vpngw2&quot; {
  source = &quot;../../modules/aws/vpngw-3.7.2/&quot;

  customer_gateway_id               = module.cgw.ids&#x5B;1]
  vpc_id                            = module.vpc.vpc_id
  vpn_gateway_id                    = module.vpc.vgw_id
  vpn_connection_static_routes_only = false
  local_ipv4_network_cidr           = &quot;0.0.0.0/0&quot;

  tunnel1_inside_cidr   = var.ip_AWST1A1
  tunnel2_inside_cidr   = var.ip_AWST2A1
  tunnel1_preshared_key = var.key_SharedKey
  tunnel2_preshared_key = var.key_SharedKey

  tags = {
    Name = var.vpn_AWSA1Name
  }

  depends_on = &#x5B;module.cgw]

}
</pre>
<p><strong>variables.tf &#8211; replace the IPs in line 50 and 59 from the previous Azure output</strong></p>
<pre class="brush: bash; collapse: true; highlight: [50,59]; light: false; title: ; toolbar: true; notranslate">
# VPC
variable &quot;rgn_RegionLocation&quot; {
  type        = string
  default     = &quot;us-east-2&quot;
  description = &quot;AWS region&quot;
}

variable &quot;vpc_Name&quot; {
  type        = string
  default     = &quot;vpcVPN&quot;
  description = &quot;Name of the VPC in AWS&quot;
}

variable &quot;vpc_CIDR&quot; {
  type        = string
  default     = &quot;10.2.0.0/16&quot;
  description = &quot;CIDR of the VPC in AWS&quot;
}

variable &quot;vpc_AZs&quot; {
  type        = list(string)
  default     = &#x5B;&quot;us-east-2a&quot;]
  description = &quot;Location of available zones&quot;
}

variable &quot;sub_pubCIDR&quot; {
  type        = list(string)
  default     = &#x5B;&quot;10.2.0.0/24&quot;]
  description = &quot;Public subnet CIDR&quot;
}

variable &quot;sub_pvtCIDR&quot; {
  type        = list(string)
  default     = &#x5B;&quot;10.2.1.0/24&quot;]
  description = &quot;Private subnet CIDR&quot;
}

# Customer gateways
variable &quot;cgw_Name&quot; {
  type        = string
  default     = &quot;cgwVPN&quot;
  description = &quot;Name of the Customer Gateway&quot;
}

####
#### REPLACE WITH OUTPUT FROM AZURE
####
variable &quot;vgw_publicip_1&quot; {
  type        = string
  default     = &quot;20.10.220.96&quot;
  description = &quot;Use the output from previous Azure script. First public IP of the Virtual Network Gateway in Azure&quot;
}

####
#### REPLACE WITH OUTPUT FROM AZURE
####
variable &quot;vgw_publicip_2&quot; {
  type        = string
  default     = &quot;4.152.124.252&quot;
  description = &quot;Use the output from previous Azure script. Second public IP of the Virtual Network Gateway in Azure&quot;
}

variable &quot;asn_AWS&quot; {
  type        = string
  default     = &quot;64512&quot;
  description = &quot;AWS ASN&quot;
}

variable &quot;asn_Azure&quot; {
  type        = string
  default     = &quot;65000&quot;
  description = &quot;Azure ASN&quot;
}

# VPN site to site 1
variable &quot;key_SharedKey&quot; {
  type        = string
  default     = &quot;KlimentAndreev1970_&quot;
  description = &quot;Make sure it&#039;s the same as vartiables.tf in  Azure folder&quot;
  # Don&#039;t go crazy with complexity. AWS side can support only 
  # between 8 and 64 characters in length and cannot start with zero (0). 
  # Allowed characters are alphanumeric characters, periods (.), and underscores (_).

}

variable &quot;vpn_AWSA0Name&quot; {
  type        = string
  default     = &quot;vpnToAzureInstance0&quot;
  description = &quot;AWS Tunnel to Azure Instance 0 name&quot;
}

variable &quot;ip_AWST1A0&quot; {
  type    = string
  default = &quot;169.254.21.0/30&quot;
}

variable &quot;ip_AWST2A0&quot; {
  type    = string
  default = &quot;169.254.22.0/30&quot;
}

# VPN site to site 2
variable &quot;vpn_AWSA1Name&quot; {
  type        = string
  default     = &quot;vpnToAzureInstance1&quot;
  description = &quot;AWS Tunnel to Azure Instance 1 name&quot;
}

variable &quot;ip_AWST1A1&quot; {
  type    = string
  default = &quot;169.254.21.4/30&quot;
}

variable &quot;ip_AWST2A1&quot; {
  type    = string
  default = &quot;169.254.22.4/30&quot;
}
</pre>
<p><strong>outputs.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
output &quot;ip_AWST1A0&quot; {
  value = module.vpngw1.vpn_connection_tunnel1_address
}

output &quot;ip_AWST2A0&quot; {
  value = module.vpngw1.vpn_connection_tunnel2_address
}

output &quot;ip_AWST1A1&quot; {
  value = module.vpngw2.vpn_connection_tunnel1_address
}

output &quot;ip_AWST2A1&quot; {
  value = module.vpngw2.vpn_connection_tunnel2_address
}
</pre>
<p>Once you run <em>terraform apply</em> you&#8217;ll get 4 IPs as an output.</p>
<pre class="brush: bash; title: ; notranslate">
ip_AWST1A0 = &quot;3.137.10.4&quot;
ip_AWST1A1 = &quot;3.20.47.74&quot;
ip_AWST2A0 = &quot;18.220.233.198&quot;
ip_AWST2A1 = &quot;18.190.37.23&quot;
</pre>
<p>You have to go back and replace these 4 IPs in <strong>variables.tf</strong> in the previous Azure folder. So, you run <em>terraform apply</em> in <strong>01.Azure</strong> folder first, then <strong>02.AWS</strong> second and then again <em>terraform apply</em> in <strong>01.Azure</strong>.</p>
<h1>Azure</h1>
<p>Edit<strong> variables.tf</strong> file and add the 4 IPs. Make sure that the variables match, do not just assign IPs randomly. Replace the IPs in the lines 155, 196, 237 and 278. The second time you run <em>terraform apply</em> it will take about a minute or two.</p>
<h1>Verification</h1>
<p>Now, go to AWS and go to <strong>VPC | VPN | Site-to-Site VPN connections</strong>. You&#8217;ll see two tunnels there. For each one select the tunnel and then click on the <strong>Tunnel details</strong> tab. Both should be up for each tunnel.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/10/P175-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-1024x241.png" alt="" width="1024" height="241" class="aligncenter size-large wp-image-10018" srcset="https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-1024x241.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-300x71.png 300w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-768x181.png 768w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-1170x275.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-03-585x138.png 585w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-03.png 1236w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
In Azure, go to <strong>Virtual Network Gateways</strong>, click on the vgw, then <strong>Monitoring </strong>and <strong>BGP peers</strong>.<br />
You should see something like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/10/P175-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-1024x538.png" alt="" width="1024" height="538" class="aligncenter size-large wp-image-10020" srcset="https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-1024x538.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-300x158.png 300w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-768x404.png 768w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-1536x808.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-1170x615.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04-585x308.png 585w, https://blog.andreev.it/wp-content/uploads/2024/10/P175-04.png 1571w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/10/aws-azure-site-to-site-s2s-vpn-tunnel-between-aws-and-azure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: Access an S3 bucket using gateway and interface endpoints (PrivateLink)</title>
		<link>https://blog.andreev.it/2024/06/aws-access-an-s3-bucket-using-gateway-and-interface-endpoints-privatelink/</link>
					<comments>https://blog.andreev.it/2024/06/aws-access-an-s3-bucket-using-gateway-and-interface-endpoints-privatelink/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 18 Jun 2024 21:19:14 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[gateway endpoint]]></category>
		<category><![CDATA[interface endpoint]]></category>
		<category><![CDATA[PrivateLink]]></category>
		<category><![CDATA[S3]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9930</guid>

					<description><![CDATA[If you have a use case where you need to transfer a lot of&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>If you have a use case where you need to transfer a lot of data back-and-forth between various resources and an S3 bucket, you will definitely benefit if you use gateway or interface endpoints for S3. The monthly bill for regional and zonal transfer will be much less and on top of that it&#8217;s much more secure.<br />
From the AWS documentation, &#8220;<em>Amazon S3 supports both gateway endpoints and interface endpoints. With a gateway endpoint, you can access Amazon S3 from your VPC, without requiring an internet gateway or NAT device for your VPC, and with no additional cost. However, gateway endpoints do not allow access from on-premises networks, from peered VPCs in other AWS Regions, or through a transit gateway. For those scenarios, you must use an interface endpoint, which is available for an additional cost.</em>&#8221;<br />
Keep this table handy as it explains the differences between gateway and interface S3 endpoint.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-1024x325.png" alt="" width="1024" height="325" class="aligncenter size-large wp-image-9934" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-1024x325.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-300x95.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-768x243.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-1170x371.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-01-585x185.png 585w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-01.png 1404w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now that we know the use case and the differences, we&#8217;ll see three different scenarios. For this, you&#8217;ll need access to two different accounts and two different regions.</p>
<ul>
<li>An S3 bucket in account A and an ec2 instance in the same account A and the same region A</li>
<li>An S3 bucket in account A and an ec2 instance in a different account B, but the same region A</li>
<li>An S3 bucket in account A and an ec2 instance in a different account B and a different region B</li>
</ul>
<p>The diagram looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-03.png" alt="" width="572" height="451" class="aligncenter size-full wp-image-9937" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-03.png 572w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-03-300x237.png 300w" sizes="(max-width: 572px) 100vw, 572px" /></a></p>
<h1>An S3 bucket in account A, ec2 in account A, both in the same region</h1>
<p>Let&#8217;s create an S3 bucket first. We&#8217;ll use AWS CLI with different profiles.</p>
<pre class="brush: bash; title: ; notranslate">
BUCKET_NAME=&quot;myuniquenameforthebucket&quot;
REGION_A=&quot;us-east-2&quot;
aws s3api create-bucket \
    --bucket $BUCKET_NAME \
    --region $REGION_A \
    --create-bucket-configuration LocationConstraint=$REGION_A
</pre>
<p>Let&#8217;s copy a file there.</p>
<pre class="brush: bash; title: ; notranslate">
dd if=/dev/zero of=somefile bs=1024 count=1
aws s3 cp somefile &quot;s3://$BUCKET_NAME/somefile&quot;
</pre>
<p>Using the same profile, create a VPC with one public subnet with the same CIDR as the VPC.</p>
<pre class="brush: bash; title: ; notranslate">
CIDR_A=&quot;192.168.10.0/24&quot;
VPCA_ID=$(aws ec2 create-vpc \
    --cidr-block $CIDR_A \
    --region $REGION_A \
    --tag-specification &#039;ResourceType=vpc,Tags=&#x5B;{Key=Name,Value=VPC-A}]&#039; \
    --output text --query &#039;Vpc.VpcId&#039;)
echo $VPCA_ID
</pre>
<p>Enable DNS and hostnames resolution, needed for SSM. Two separate lines needed.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 modify-vpc-attribute --enable-dns-hostnames &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCA_ID
aws ec2 modify-vpc-attribute --enable-dns-support &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCA_ID
</pre>
<p>Create a default network ACL.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 create-network-acl --vpc-id $VPCA_ID
</pre>
<p>Create an Internet Gateway and attach it to the VPC.</p>
<pre class="brush: bash; title: ; notranslate">
IGWA_ID=$(aws ec2 create-internet-gateway \
    --region $REGION_A \
    --tag-specifications &#039;ResourceType=internet-gateway,Tags=&#x5B;{Key=Name,Value=igw-A}]&#039; \
    --output text --query &#039;InternetGateway.InternetGatewayId&#039;)
echo $IGWA_ID
aws ec2 attach-internet-gateway \
    --internet-gateway-id $IGWA_ID \
    --vpc-id $VPCA_ID
</pre>
<p>Let&#8217;s create a subnet.</p>
<pre class="brush: bash; title: ; notranslate">
SUBA_ID=$(aws ec2 create-subnet \
    --vpc-id $VPCA_ID \
    --cidr-block 192.168.10.0/24 \
    --region $REGION_A \
    --tag-specifications &#039;ResourceType=subnet,Tags=&#x5B;{Key=Name,Value=subPubA}]&#039; \
    --output text --query &#039;Subnet.SubnetId&#039;)
echo $SUBA_ID
</pre>
<p>Create a route table.</p>
<pre class="brush: bash; title: ; notranslate">
RTA_ID=$(aws ec2 create-route-table \
     --vpc-id $VPCA_ID \
     --output text --query &#039;RouteTable.RouteTableId&#039;)
echo $RTA_ID
aws ec2 create-route --route-table-id $RTA_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGWA_ID
</pre>
<p>Associate this route table to the public subnet.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 associate-route-table --route-table-id $RTA_ID --subnet-id $SUBA_ID
</pre>
<p>Now, we have to create an EC2 instance where we can test the S3 access. We won&#8217;t be using keys and SSH to access, we&#8217;ll use SSM. For that we need the instance to have access to the Internet which means we need a security group that allows access to Internet.</p>
<pre class="brush: bash; title: ; notranslate">
SGA_ID=$(aws ec2 create-security-group \
    --group-name sgAllowICMPandOutboundAccess --description &quot;Allows ICMP and outbound access&quot; \
    --vpc-id $VPCA_ID \
    --output text --query &#039;GroupId&#039;)
echo $SGA_ID
</pre>
<p>Allow unrestricted egress. By default this rule is there, so don&#8217;t execute the code below.</p>
<pre class="brush: bash; title: ; notranslate">
#aws ec2 authorize-security-group-egress \
    --group-id $SGA_ID \
    --protocol all \
    --cidr &quot;0.0.0.0/0&quot; 
</pre>
<p>Do this one, so we can ping instances from each other when we do VPC peering.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 authorize-security-group-ingress \
  --group-id $SGA_ID \
  --ip-permissions IpProtocol=icmp,FromPort=-1,ToPort=-1,IpRanges=&quot;&#x5B;{CidrIp=192.168.10.0/24},{CidrIp=192.168.20.0/24},{CidrIp=192.168.30.0/24}]&quot;
</pre>
<p>Let&#8217;s create a role so we can access the EC2 over SSM. First we need a policy that the role can assume.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; &#039;EOF&#039; &gt;&gt; trust-policy.json
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: &#x5B;
    {
      &quot;Sid&quot;: &quot;&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Service&quot;: &quot;ec2.amazonaws.com&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
EOF
</pre>
<p>Then create the role and attach the AWS Managed policy for SSM (<strong>AmazonSSMManagerInstanceCore</strong>).</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-role --role-name rolSSM --assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore --role-name rolSSM
</pre>
<p>Create instance profile.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-instance-profile --instance-profile-name ipEC2
</pre>
<p>Add the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam add-role-to-instance-profile --role-name rolSSM --instance-profile-name ipEC2
</pre>
<p>Create an instance.</p>
<pre class="brush: bash; title: ; notranslate">
AMI_ID=&quot;ami-033fabdd332044f06&quot;
INS_TYPE=&quot;t3.micro&quot;
aws ec2 run-instances \
    --image-id $AMI_ID \
    --instance-type $INS_TYPE \
    --security-group-ids $SGA_ID \
    --subnet-id $SUBA_ID \
    --iam-instance-profile Name=&quot;ipEC2&quot; \
    --associate-public-ip-address  \
    --tag-specifications &#039;ResourceType=instance,Tags=&#x5B;{Key=Name,Value=ec2-A}]&#039;
</pre>
<p>We&#8217;ll need an IAM user that we&#8217;ll use to access the S3 bucket.</p>
<pre class="brush: bash; title: ; notranslate">
IAM_USER=&quot;usrMyBucket&quot;
aws iam create-user --user-name $IAM_USER
</pre>
<p>We&#8217;ll also need a policy that gives access to that specific bucket only.</p>
<pre class="brush: bash; title: ; notranslate">
cat &gt;&gt; bucket-policy.json &lt;&lt; EOF
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;s3:*&quot;,
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:s3:::$BUCKET_NAME&quot;,
                &quot;arn:aws:s3:::$BUCKET_NAME/*&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Deny&quot;,
            &quot;NotAction&quot;: &quot;s3:*&quot;,
            &quot;NotResource&quot;: &#x5B;
                &quot;arn:aws:s3:::$BUCKET_NAME&quot;,
                &quot;arn:aws:s3:::$BUCKET_NAME/*&quot;
            ]
        }
    ]
}
EOF
</pre>
<p>Create the policy.</p>
<pre class="brush: bash; title: ; notranslate">
POL_ARN=$(aws iam create-policy \
    --policy-name polMyBucketAccess \
    --policy-document file://bucket-policy.json \
    --output text --query &#039;Policy.Arn&#039;)
echo $POL_ARN
</pre>
<p>Attach the policy to the user.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam attach-user-policy --policy-arn $POL_ARN --user-name $IAM_USER
</pre>
<p>Create access keys.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-access-key --user-name $IAM_USER --output table --query &#x5B;&#039;AccessKey.AccessKeyId&#039;,&#039;AccessKey.SecretAccessKey&#039;]
</pre>
<p>Copy these two values.<br />
Now, go to AWS console, select the EC2 instance and click <strong>Connect</strong>. Use the 2nd tab <strong>Session Manager</strong> and click <strong>Connect</strong>.<br />
Type <em>aws configure</em> and paste the copied values for the IAM user you just created.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-04.png" alt="" width="940" height="183" class="aligncenter size-full wp-image-9967" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-04.png 940w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-04-300x58.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-04-768x150.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-04-585x114.png 585w" sizes="(max-width: 940px) 100vw, 940px" /></a><br />
If you try to do <em>aws s3 ls</em>, you&#8217;ll get access denied, but if you try <em>aws s3 ls s3://myuniquenameforthebucket</em>, you&#8217;ll see that it works.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-05.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-05-1024x159.png" alt="" width="1024" height="159" class="aligncenter size-large wp-image-9968" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-05-1024x159.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-05-300x47.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-05-768x119.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-05-585x91.png 585w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-05.png 1158w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, this type of access goes over the Internet and you&#8217;ll be charged for the data transfers. In order to avoid that, we&#8217;ll create an S3 gateway endpoint.<br />
Go to <strong>VPC </strong>menu and from the left side choose <strong>Endpoints</strong>.<br />
Create the endpoint, name the endpoint, choose <strong>AWS services</strong>, filter by <strong>Type = Gateway</strong> and select the S3 service name. Then select the VPC where you created your EC2 and the route table that&#8217;s associated with the subnet where EC2 resides. For policy choose Full Access. This means, the S3 gateway endpoint will have access to all S3 buckets. Finally, click to create the endpoint.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-06.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-1024x573.png" alt="" width="1024" height="573" class="aligncenter size-large wp-image-9969" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-1024x573.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-300x168.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-768x430.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-1536x860.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-2048x1146.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-1920x1075.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-1170x655.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-06-585x327.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you check your route table, you&#8217;ll see that there is another entry there. Wait 10 seconds or less if you don&#8217;t see it.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-07.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-1024x650.png" alt="" width="1024" height="650" class="aligncenter size-large wp-image-9970" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-1024x650.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-300x190.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-768x488.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-1170x743.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-07-585x371.png 585w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-07.png 1315w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you go back to the EC2 instance and do the <em>aws s3 ls s3://myuniquenameforthebucket</em> command again, you&#8217;ll see that nothing changed. So, how do you know that we did the right thing? It&#8217;s simple. Click on the endpoint that you&#8217;ve created, click on the <strong>Policy </strong>tab and then click the <strong>Edit Policy</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-08.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-1024x561.png" alt="" width="1024" height="561" class="aligncenter size-large wp-image-9971" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-1024x561.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-300x164.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-768x421.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-1536x842.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-2048x1122.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-1920x1052.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-1170x641.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-08-585x321.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Change line 5 from <em>Allow </em>to <em>Deny </em>and click <strong>Save</strong>.<br />
Go back to the EC2 instance and do the <em>aws s3 ls s3://myuniquenameforthebucket</em> again. You&#8217;ll get access denied. There you go&#8230;Traffic goes over the endpoint. Revert the changes back from <em>Deny </em>to <em>Allow</em>. Let&#8217;s move to the 2nd use case.</p>
<h1>An S3 bucket in account A, an ec2 instance in a different account B, but both in the same region A</h1>
<p><strong>NOTE:Make sure you switch your AWS CLI to a different profile, because we&#8217;ll be building the EC2 instance in another account.</strong> The S3 bucket stays the same.<br />
Let&#8217;s build the 2nd VPC and EC2.</p>
<pre class="brush: bash; title: ; notranslate">
REGION_A=&quot;us-east-2&quot;
CIDR_B=&quot;192.168.20.0/24&quot;
VPCB_ID=$(aws ec2 create-vpc \
    --cidr-block $CIDR_B \
    --region $REGION_A \
    --tag-specification &#039;ResourceType=vpc,Tags=&#x5B;{Key=Name,Value=VPC-B}]&#039; \
    --output text --query &#039;Vpc.VpcId&#039;)
echo $VPCB_ID
</pre>
<p>Enable DNS and hostnames resolution, needed for SSM. Two separate lines needed.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 modify-vpc-attribute --enable-dns-hostnames &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCB_ID
aws ec2 modify-vpc-attribute --enable-dns-support &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCB_ID
</pre>
<p>Create a default network ACL.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 create-network-acl --vpc-id $VPCB_ID
</pre>
<p>Create an Internet Gateway and attach it to the VPC.</p>
<pre class="brush: bash; title: ; notranslate">
IGWB_ID=$(aws ec2 create-internet-gateway \
    --region $REGION_A \
    --tag-specifications &#039;ResourceType=internet-gateway,Tags=&#x5B;{Key=Name,Value=igw-B}]&#039; \
    --output text --query &#039;InternetGateway.InternetGatewayId&#039;)
echo $IGWB_ID
aws ec2 attach-internet-gateway \
    --internet-gateway-id $IGWB_ID \
    --vpc-id $VPCB_ID
</pre>
<p>Let&#8217;s create a subnet.</p>
<pre class="brush: bash; title: ; notranslate">
SUBB_ID=$(aws ec2 create-subnet \
    --vpc-id $VPCB_ID \
    --cidr-block 192.168.20.0/24 \
    --region $REGION_A \
    --tag-specifications &#039;ResourceType=subnet,Tags=&#x5B;{Key=Name,Value=subPubB}]&#039; \
    --output text --query &#039;Subnet.SubnetId&#039;)
echo $SUBB_ID
</pre>
<p>Create a route table.</p>
<pre class="brush: bash; title: ; notranslate">
RTB_ID=$(aws ec2 create-route-table \
     --vpc-id $VPCB_ID \
     --output text --query &#039;RouteTable.RouteTableId&#039;)
echo $RTB_ID
aws ec2 create-route --route-table-id $RTB_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGWB_ID
</pre>
<p>Associate this route table to the public subnet.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 associate-route-table --route-table-id $RTB_ID --subnet-id $SUBB_ID
</pre>
<p>Now, we have to create an EC2 instance where we can test the S3 access. We won&#8217;t be using keys and SSH to access, we&#8217;ll use SSM. For that we need the instance to have access to the Internet which means we need a security group that allows access to Internet.</p>
<pre class="brush: bash; title: ; notranslate">
SGB_ID=$(aws ec2 create-security-group \
    --group-name sgAllowICMPandOutboundAccess --description &quot;Allows ICMP and outbound access&quot; \
    --vpc-id $VPCB_ID \
    --output text --query &#039;GroupId&#039;)
echo $SGB_ID
</pre>
<p>Allow unrestricted egress. By default this rule is there, so don&#8217;t execute the code below.</p>
<pre class="brush: bash; title: ; notranslate">
#aws ec2 authorize-security-group-egress \
    --group-id $SGB_ID \
    --protocol all \
    --cidr &quot;0.0.0.0/0&quot; 
</pre>
<p>Do this one, so we can ping instances from each other when we do VPC peering.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 authorize-security-group-ingress \
  --group-id $SGB_ID \
  --ip-permissions IpProtocol=icmp,FromPort=-1,ToPort=-1,IpRanges=&quot;&#x5B;{CidrIp=192.168.10.0/24},{CidrIp=192.168.20.0/24},{CidrIp=192.168.30.0/24}]&quot;
</pre>
<p>We can reuse the same policy to create a role that will allow us to use SSM.</p>
<pre class="brush: bash; title: ; notranslate">
cat trust-policy.json
</pre>
<p>Then create the role and attach the AWS Managed policy for SSM (<strong>AmazonSSMManagerInstanceCore</strong>).</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-role --role-name rolSSM --assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore --role-name rolSSM
</pre>
<p>Create instance profile. We&#8217;ll use the same names, because they are different accounts.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-instance-profile --instance-profile-name ipEC2
</pre>
<p>Add the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam add-role-to-instance-profile --role-name rolSSM --instance-profile-name ipEC2
</pre>
<p>Create an instance. The AMI_ID is the same because it&#8217;s the same region.</p>
<pre class="brush: bash; title: ; notranslate">
AMI_ID=&quot;ami-033fabdd332044f06&quot;
INS_TYPE=&quot;t3.micro&quot;
aws ec2 run-instances \
    --image-id $AMI_ID \
    --instance-type $INS_TYPE \
    --security-group-ids $SGB_ID \
    --subnet-id $SUBB_ID \
    --iam-instance-profile Name=&quot;ipEC2&quot; \
    --associate-public-ip-address  \
    --tag-specifications &#039;ResourceType=instance,Tags=&#x5B;{Key=Name,Value=ec2-B}]&#039;
</pre>
<p>Wait for a couple of minutes and then connect from SSM, same as you did with the first instance. Configure AWS again using the same set of credentials that you did before and then try to list the content of the bucket. You should be able to see the file.</p>
<pre class="brush: bash; title: ; notranslate">
BUCKET_NAME=&quot;myuniquenameforthebucket&quot;
aws s3 ls s3://$BUCKET_NAME
</pre>
<p>Again, the traffic goes over the Internet. So we need another gateway S3 endpoint. You create the gateway S3 endpoint where the client is located, not the destination.<br />
Let&#8217;s create another S3 gateway endpoint as described above.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-09.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-1024x505.png" alt="" width="1024" height="505" class="aligncenter size-large wp-image-9977" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-1024x505.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-300x148.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-768x379.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-1536x758.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-1920x948.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-1170x577.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09-585x289.png 585w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-09.png 2020w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Check the route table and you&#8217;ll see the route created. At this point, you are accessing the bucket over the endpoint. Do the same test. Change the policy to Deny for the endpoint and you&#8217;ll get access denied.</p>
<h1>An S3 bucket in account A, an ec2 instance in a different account B and a different region B</h1>
<p>For the 3rd use case, we&#8217;ll use an interface endpoint (PrivateLink), not a gateway endpoint. This is what AWS documentation has to say. &#8220;<em>With AWS PrivateLink for Amazon S3, you can provision interface VPC endpoints (interface endpoints) in your virtual private cloud (VPC). These endpoints are directly accessible from applications that are on premises over VPN and AWS Direct Connect, or in a different AWS Region over VPC peering. Interface endpoints are represented by one or more elastic network interfaces (ENIs) that are assigned private IP addresses from subnets in your VPC. Requests to Amazon S3 over interface endpoints stay on the Amazon network. You can also access interface endpoints in your VPC from on-premises applications through AWS Direct Connect or AWS Virtual Private Network (AWS VPN).</em>&#8221;<br />
Let&#8217;s create the resources. VPC and EC2. Change the AWS CLI profile so it&#8217;s using the same account B and a new region B.</p>
<pre class="brush: bash; title: ; notranslate">
REGION_B=&quot;us-east-1&quot;
CIDR_C=&quot;192.168.30.0/24&quot;
VPCC_ID=$(aws ec2 create-vpc \
    --cidr-block $CIDR_C \
    --region $REGION_B \
    --tag-specification &#039;ResourceType=vpc,Tags=&#x5B;{Key=Name,Value=VPC-C}]&#039; \
    --output text --query &#039;Vpc.VpcId&#039;)
echo $VPCC_ID
</pre>
<p>Enable DNS and hostnames resolution, needed for SSM. Two separate lines needed.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 modify-vpc-attribute --enable-dns-hostnames &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCC_ID
aws ec2 modify-vpc-attribute --enable-dns-support &quot;{\&quot;Value\&quot;:true}&quot;  --vpc-id $VPCC_ID
</pre>
<p>Create a default network ACL.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 create-network-acl --vpc-id $VPCC_ID
</pre>
<p>Create an Internet Gateway and attach it to the VPC.</p>
<pre class="brush: bash; title: ; notranslate">
IGWC_ID=$(aws ec2 create-internet-gateway \
    --region $REGION_B \
    --tag-specifications &#039;ResourceType=internet-gateway,Tags=&#x5B;{Key=Name,Value=igw-C}]&#039; \
    --output text --query &#039;InternetGateway.InternetGatewayId&#039;)
echo $IGWC_ID
aws ec2 attach-internet-gateway \
    --internet-gateway-id $IGWC_ID \
    --vpc-id $VPCC_ID
</pre>
<p>Let&#8217;s create a subnet.</p>
<pre class="brush: bash; title: ; notranslate">
SUBC_ID=$(aws ec2 create-subnet \
    --vpc-id $VPCC_ID \
    --cidr-block 192.168.30.0/24 \
    --region $REGION_B \
    --tag-specifications &#039;ResourceType=subnet,Tags=&#x5B;{Key=Name,Value=subPubC}]&#039; \
    --output text --query &#039;Subnet.SubnetId&#039;)
echo $SUBC_ID
</pre>
<p>Create a route table.</p>
<pre class="brush: bash; title: ; notranslate">
RTC_ID=$(aws ec2 create-route-table \
     --vpc-id $VPCC_ID \
     --output text --query &#039;RouteTable.RouteTableId&#039;)
echo $RTC_ID
aws ec2 create-route --route-table-id $RTC_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGWC_ID
</pre>
<p>Associate this route table to the public subnet.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 associate-route-table --route-table-id $RTC_ID --subnet-id $SUBC_ID
</pre>
<p>Now, we have to create an EC2 instance where we can test the S3 access. We won&#8217;t be using keys and SSH to access, we&#8217;ll use SSM. For that we need the instance to have access to the Internet which means we need a security group that allows access to Internet.</p>
<pre class="brush: bash; title: ; notranslate">
SGC_ID=$(aws ec2 create-security-group \
    --group-name sgAllowICMPandOutboundAccess --description &quot;Allows ICMP and outbound access&quot; \
    --vpc-id $VPCC_ID \
    --output text --query &#039;GroupId&#039;)
echo $SGC_ID
</pre>
<p>Allow unrestricted egress. By default this rule is there, so don&#8217;t execute the code below.</p>
<pre class="brush: bash; title: ; notranslate">
#aws ec2 authorize-security-group-egress \
    --group-id $SGB_ID \
    --protocol all \
    --cidr &quot;0.0.0.0/0&quot; 
</pre>
<p>Do this one, so we can ping instances from each other when we do VPC peering.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 authorize-security-group-ingress \
  --group-id $SGC_ID \
  --ip-permissions IpProtocol=icmp,FromPort=-1,ToPort=-1,IpRanges=&quot;&#x5B;{CidrIp=192.168.10.0/24},{CidrIp=192.168.20.0/24},{CidrIp=192.168.30.0/24}]&quot;
</pre>
<p>We don&#8217;t have to create any roles. We&#8217;ll use the existing ones that we built for the 2nd use case.<br />
Create the EC2. The AMI ID will change this time.</p>
<pre class="brush: bash; title: ; notranslate">
AMI_ID=&quot;ami-08a0d1e16fc3f61ea&quot;
INS_TYPE=&quot;t3.micro&quot;
aws ec2 run-instances \
    --image-id $AMI_ID \
    --instance-type $INS_TYPE \
    --security-group-ids $SGC_ID \
    --subnet-id $SUBC_ID \
    --iam-instance-profile Name=&quot;ipEC2&quot; \
    --associate-public-ip-address  \
    --tag-specifications &#039;ResourceType=instance,Tags=&#x5B;{Key=Name,Value=ec2-C}]&#039;
</pre>
<p>Same thing as we did before, connect from SSM, configure AWS CLI using the previous credentials and try to list the bucket.</p>
<pre class="brush: bash; title: ; notranslate">
BUCKET_NAME=&quot;myuniquenameforthebucket&quot;
aws s3 ls s3://$BUCKET_NAME
</pre>
<p>You should see the file there. But, this access goes over Internet. Now, we have to create an interface endpoint in the destination VPC which is VPC-A. When we create an interface endpoint a private IP will be generated for this interface endpoint. In order this private IP to access the bucket, we need some sort of connection between regions, e.g. VPC peering or Transit Gateway. Let&#8217;s do the peering between account A (VPC-A) and account B (VPC-C). Enter the 12 digit account number for account A where the S3 bucket resides.</p>
<pre class="brush: bash; title: ; notranslate">
echo $VPCA_ID
echo $VPCC_ID
echo $REGION_A
ACCOUNT_A=&quot;123456789012&quot;
echo $ACCOUNT_A
</pre>
<p>Make sure all of the above returns some value. Do the peering.</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 create-vpc-peering-connection \
  --vpc-id $VPCC_ID --peer-vpc-id $VPCA_ID \
  --peer-owner-id $ACCOUNT_A --peer-region $REGION_A
</pre>
<p>Log to the account A and under <strong>VPC | Peering</strong> menu on the left, select the peering and accept it. But this is not enough. You need to tell the VPC to use the routes over the peering connection. Go to account A, VPC A, find the route for the subnet where the EC2 instance is and add a route for destination 192.168.30.0/24 to go over peering. As you can see this is the same route that we modified initially in our first use case.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-10.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-1024x575.png" alt="" width="1024" height="575" class="aligncenter size-large wp-image-9985" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-1024x575.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-300x168.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-768x431.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-1200x675.png 1200w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-1170x657.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10-585x328.png 585w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-10.png 1290w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, log to account B and VPC C and modify the route but this time, the destination is VPC A (192.168.10.0/24). At this point you should be able to ping EC2-A from EC2-C. Don&#8217;t worry if you can&#8217;t ping EC2-C from EC2-A.<br />
At this point we can create the interface endpoint where the bucket is. This time choose interface as type, VPC-A, our security group, full access policy and the subnet where EC2 is. This subnet has nothing to do with the bucket access, we are just creating a network interface there. We can choose any subnet in that VPC, we just need the IP assigned to the endpoint interface.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-11.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-1024x835.png" alt="" width="1024" height="835" class="aligncenter size-large wp-image-9994" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-1024x835.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-300x245.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-768x626.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-1536x1252.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-2048x1669.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-1920x1565.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-1170x954.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-11-585x477.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
There will be 2 DNS names created for you. One is referring the zone and the other the region. Use the regional DNS name, the first one and add the word bucket instead of the * character.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/06/P174-12.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/06/P174-12.png" alt="" width="913" height="526" class="aligncenter size-full wp-image-9995" srcset="https://blog.andreev.it/wp-content/uploads/2024/06/P174-12.png 913w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-12-300x173.png 300w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-12-768x442.png 768w, https://blog.andreev.it/wp-content/uploads/2024/06/P174-12-585x337.png 585w" sizes="(max-width: 913px) 100vw, 913px" /></a><br />
From instance EC2-C, this is how you will access the bucket.</p>
<pre class="brush: bash; title: ; notranslate">
aws s3 ls s3://myuniquenameforthebucket --endpoint-url https://bucket.vpce-038d5f94d78d8b216-vyvk5uon.s3.us-east-2.vpce.amazonaws.com
</pre>
<p>If you try from the instance EC2-C, this won&#8217;t work. Why? Because of the security group that we used. We have to allow HTTPS traffic from 192.168.30.0/24. Go find the first security group, that&#8217;s the one we used for the interface endpoint and allow HTTPS from 192.168.30.0/24. After the change, you&#8217;ll be able to access the bucket.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/06/aws-access-an-s3-bucket-using-gateway-and-interface-endpoints-privatelink/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Entra ID (Azure AD) SSO and Azure Static Web Apps</title>
		<link>https://blog.andreev.it/2024/03/entra-id-azure-ad-sso-and-azure-static-web-apps/</link>
					<comments>https://blog.andreev.it/2024/03/entra-id-azure-ad-sso-and-azure-static-web-apps/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 31 Mar 2024 17:15:00 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Azure AD]]></category>
		<category><![CDATA[Azure Static Web Apps]]></category>
		<category><![CDATA[entra ID]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9861</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to configure a static web app in Azure&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to configure a static web app in Azure and put the app behind Entra ID (Azure AD) SSO authentication. You&#8217;ll have the same protection as logging with your Entra ID credentials to Azure and Office 365. There is no need for any code change in the app. As long as you are an user or a member of an Entra ID group assigned to the Enterprise Application, then you can access the Static Web App URL. I&#8217;ll use Azure DevOps to host the files for the web site, but you can use any other repo. </p>
<h1>Azure DevOps</h1>
<p>Log to your ADO organization and create a new project. I called mine <em>Static Web App</em>. Click on <strong>Initialize </strong>and then copy the path so you can clone it locally.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-1024x836.png" alt="" width="1024" height="836" class="aligncenter size-large wp-image-9872" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-1024x836.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-300x245.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-768x627.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-1170x955.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-01-585x478.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-01.png 1357w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Once cloned, go to the repo locally and you&#8217;ll see a README.md file. Clone this web site template or just create an index.html file. </p>
<pre class="brush: bash; title: ; notranslate">
git clone https://github.com/designmodo/html-website-templates.git
</pre>
<p>Then move the index file back to the root of our repo.</p>
<pre class="brush: bash; title: ; notranslate">
mv html-website-templates/Animated\ Landing\ Page\ Website\ Template/* .
rm -rf html-website-templates/
</pre>
<p>You should have the index.html file in the root of our repo. Push to ADO.</p>
<pre class="brush: bash; title: ; notranslate">
git add .
git commit -m &quot;Updated website&quot;
git push
</pre>
<h1>Azure Static Web App</h1>
<p>Log to your Azure subscription and create or use an existing resource group. I&#8217;ll call mine <strong>rgStaticWebApp</strong>. Go to the <strong>Static Web Apps</strong> menu and create a new <strong>Static Web App</strong>. Specify a subscription, a resource group and name it. In my case it&#8217;s called <em>mystaticwebapp</em>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-02.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-02-1024x506.png" alt="" width="1024" height="506" class="aligncenter size-large wp-image-9874" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-02-1024x506.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-02-300x148.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-02-768x379.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-02-585x289.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-02.png 1156w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Make sure you choose the <strong>Standard hosting plan</strong>. SSO won&#8217;t work with the free plan. Choose the repo location, the branch and <strong>HTML </strong>for <strong>Build Preset</strong>.<br />
Use the defaults for the rest and click on <strong>Create</strong>. The static web site will be deployed in 5-10 seconds and if you go to the resource, click <strong>Overview</strong>, you&#8217;ll see the URL of your website. Go to that URL and you&#8217;ll see our website is public.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-04.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-1024x642.jpg" alt="" width="1024" height="642" class="aligncenter size-large wp-image-9875" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-1024x642.jpg 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-300x188.jpg 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-768x482.jpg 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-1536x963.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-1920x1204.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-1170x734.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-04-585x367.jpg 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Custom domain (optional)</h1>
<p>In case you want a custom domain, then these changes are required. This is not a requirement for the SSO, but it&#8217;s better if you have your own domain URL.<br />
Click on <strong>Custom Domains</strong> and then choose where is your domain hosted. In my case I have it with CloudFlare.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-05.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-05.png" alt="" width="895" height="603" class="aligncenter size-full wp-image-9876" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-05.png 895w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-05-300x202.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-05-768x517.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-05-585x394.png 585w" sizes="(max-width: 895px) 100vw, 895px" /></a><br />
Enter how you want to access your website, in my case it is <em>mysite.andreev.it</em>.<br />
You&#8217;ll have to create a CNAME in CloudFlare.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-06.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-06.png" alt="" width="838" height="501" class="aligncenter size-full wp-image-9882" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-06.png 838w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-06-300x179.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-06-768x459.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-06-585x350.png 585w" sizes="(max-width: 838px) 100vw, 838px" /></a><br />
Do not proxy it thru CloudFlare for now.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-07.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-1024x116.png" alt="" width="1024" height="116" class="aligncenter size-large wp-image-9883" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-1024x116.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-300x34.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-768x87.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-1170x133.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-07-585x66.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-07.png 1306w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Add </strong>at the bottom and it might take some time for Azure to verify the new CNAME record. In my case it took less than a minute.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-08.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-08.png" alt="" width="808" height="589" class="aligncenter size-full wp-image-9884" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-08.png 808w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-08-300x219.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-08-768x560.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-08-585x426.png 585w" sizes="(max-width: 808px) 100vw, 808px" /></a><br />
&#8230;and if you go to <em>whateveryou.namedyour.site</em>, you should see the same static web page.</p>
<h1>Entra ID (Azure AD) SSO</h1>
<p>In order to add SSO, we have to add a file in the root of our static web site and then make some changes in Azure. Refer to this <a href="https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad%2Cinvitations" rel="noopener" target="_blank">link </a>for more information.<br />
Create the following file in the root of the static web site and name it <strong>staticwebapp.config.json</strong>. It has to be this name.<br />
This is what the file looks like.</p>
<pre class="brush: xml; highlight: [18,19,20]; title: ; notranslate">
{
  &quot;routes&quot;: &#x5B;
    {
      &quot;route&quot;: &quot;/*&quot;,
      &quot;allowedRoles&quot;: &#x5B;&quot;authenticated&quot;]
    }
  ],
  &quot;responseOverrides&quot;: {
    &quot;401&quot;: {
      &quot;statusCode&quot;: 302,
      &quot;redirect&quot;: &quot;/.auth/login/aad&quot;
    }
  },
  &quot;auth&quot;: {
    &quot;identityProviders&quot;: {
      &quot;azureActiveDirectory&quot;: {
        &quot;registration&quot;: {
          &quot;openIdIssuer&quot;: &quot;https://login.microsoftonline.com/TENANT_ID/v2.0&quot;,
          &quot;clientIdSettingName&quot;: &quot;AZURE_CLIENT_ID&quot;,
          &quot;clientSecretSettingName&quot;: &quot;AZURE_CLIENT_SECRET&quot;
        }
      }
    }
  }
}
</pre>
<p>We need 3 values. The first value, <strong>TENANT_ID</strong> needs to go to this file hardcoded and the other two values for the <strong>CLIENT_ID</strong> and <strong>CLIENT_SECRET</strong> will be added in Azure. Here is how to fine these values. Go to Entra ID and click <strong>Overview</strong>. This is your <strong>TENANT_ID</strong> value. Copy and paste that value in line 18 and save the file. After, <em>git add/commit/push</em> the file back to Azure DevOps repo.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-09.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-1024x572.png" alt="" width="1024" height="572" class="aligncenter size-large wp-image-9890" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-1024x572.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-300x168.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-768x429.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-1170x654.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-09-585x327.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-09.png 1204w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>App Registrations</strong> and click on <strong>New registration</strong>.<br />
Enter these values and click <strong>Register</strong>. Replace the URL with yours even if it&#8217;s not a custom domain. Add <strong>.auth/login/aad/callback</strong> as a suffix.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-10.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-10-802x1024.png" alt="" width="802" height="1024" class="aligncenter size-large wp-image-9896" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-10-802x1024.png 802w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-10-235x300.png 235w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-10-768x980.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-10-585x747.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-10.png 1035w" sizes="(max-width: 802px) 100vw, 802px" /></a><br />
The app registers right away and if you click the <strong>Overview </strong>you&#8217;ll see the app client ID. Copy this value in notepad or somewhere.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-12.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-1024x488.png" alt="" width="1024" height="488" class="aligncenter size-large wp-image-9891" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-1024x488.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-300x143.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-768x366.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-1170x557.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-12-585x279.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-12.png 1279w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
From the menu on the left, click <strong>Certificates &#038; secrets</strong> and then click on <strong>New client secret</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-13.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-13-1024x611.png" alt="" width="1024" height="611" class="aligncenter size-large wp-image-9892" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-13-1024x611.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-13-300x179.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-13-768x458.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-13-585x349.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-13.png 1027w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Name the secret and choose for how long it&#8217;s valid.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-14.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-14.png" alt="" width="841" height="253" class="aligncenter size-full wp-image-9893" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-14.png 841w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-14-300x90.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-14-768x231.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-14-585x176.png 585w" sizes="(max-width: 841px) 100vw, 841px" /></a><br />
You&#8217;ll see that a secret has been created and you&#8217;ll have a value and a secret ID. Copy the value not the ID in notepad or somewhere.<br />
Now, go to the Static Web App and click on Environment variables. Add both <strong>AZURE_CLIENT_ID</strong> and <strong>AZURE_CLIENT_SECRET</strong> and click <strong>Apply </strong>at the bottom.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-15.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-1024x537.png" alt="" width="1024" height="537" class="aligncenter size-large wp-image-9894" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-1024x537.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-300x157.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-768x403.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-1170x614.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-15-585x307.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-15.png 1246w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Finally, go to <strong>App Registration</strong>, find your app there, click <strong>Authentication </strong>on the left and make sure that <strong>ID tokens</strong> option is checked.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-11.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-1024x739.png" alt="" width="1024" height="739" class="aligncenter size-large wp-image-9898" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-1024x739.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-300x216.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-768x554.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-1536x1108.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-1170x844.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11-585x422.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-11.png 1561w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, if you go to the URL of the app, you&#8217;ll be prompted to log in and after successfully logging in, you&#8217;ll see our static web page. But every user from your tenant can log in, the only restriction is that you are member of the Entra ID tenant. If you want to restrict the website only to specific people, do the following. Go to <strong>Enterprise Applications</strong>, find your application and click on <strong>Properties</strong>. Make sure <strong>Assignment required</strong> is set to <strong>Yes</strong>. Click <strong>Save </strong>on top.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P172-16.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-1024x602.png" alt="" width="1024" height="602" class="aligncenter size-large wp-image-9899" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-1024x602.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-300x176.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-768x451.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-1536x902.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-1170x687.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16-585x344.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P172-16.png 1590w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Then, click on <strong>Users and Groups</strong> and assign a group or users to the app. Mind that if you use the free version of Entra ID, you won&#8217;t be able to assign groups, only users.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/03/entra-id-azure-ad-sso-and-azure-static-web-apps/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Okta and AWS SSO (AWS IAM Identity Center)</title>
		<link>https://blog.andreev.it/2024/03/okta-and-aws-sso-aws-iam-identity-center/</link>
					<comments>https://blog.andreev.it/2024/03/okta-and-aws-sso-aws-iam-identity-center/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Wed, 20 Mar 2024 17:25:23 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[AWS IAM Identity Center]]></category>
		<category><![CDATA[OKTA]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9783</guid>

					<description><![CDATA[In this post I&#8217;ll describe how to integrate Okta and your AWS account. In&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll describe how to integrate Okta and your AWS account. In order everything to work, you&#8217;ll have to enable AWS Organization on the master account and then you can add multiple sub accounts in AWS.<br />
Best practice says that the AWS master account should be used for SSO only and you shouldn&#8217;t run any workloads. First thing first, let&#8217;s start with Okta. </p>
<h1>Okta</h1>
<p>Log to your Okta account or sign up for a 30 day trial. Once logged in, if you haven&#8217;t already done so, change the sign-in domain from trail*-okta.com to your domain. Or you can skip this step.<br />
Click on <strong>Admin </strong>from the upper right corner and then under <strong>Customizations</strong>, click on <strong>Domain</strong>. I&#8217;ve used the Okta-managed customization because it&#8217;s easier to manage.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-1024x507.png" alt="" width="1024" height="507" class="aligncenter size-large wp-image-9784" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-1024x507.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-300x148.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-768x380.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-1536x760.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-2048x1014.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-1920x950.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-1170x579.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-01-585x290.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
After you click <strong>Next</strong>, you&#8217;ll be prompted to add two DNS entries for your domain.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-02.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-1024x575.png" alt="" width="1024" height="575" class="aligncenter size-large wp-image-9785" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-1024x575.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-300x168.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-768x431.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-1536x862.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-1200x675.png 1200w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-1170x657.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02-585x328.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-02.png 1579w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Change the DNS. This will allow us to access Okta dashboard using our domain in my case it&#8217;s okta.andreev.it. From the same menu on the left, click on <strong>Applications</strong>, then <strong>Applications </strong>again and click on <strong>Browse App Catalog</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-1024x584.png" alt="" width="1024" height="584" class="aligncenter size-large wp-image-9824" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-1024x584.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-300x171.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-768x438.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-1170x668.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-03-585x334.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-03.png 1230w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Search for <strong>AWS IAM</strong> and choose <strong>AWS IAM Identity Center</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-04-1024x477.png" alt="" width="1024" height="477" class="aligncenter size-large wp-image-9825" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-04-1024x477.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-04-300x140.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-04-768x358.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-04-585x273.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-04.png 1120w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Add Integration</strong> and then <strong>Done</strong>.<br />
Click on the<strong> Sign On</strong> tab and scroll all the way down to <strong>SAML Signing Certificates</strong>. Click on <strong>Actions </strong>drop-down and then <strong>View IdP metadata</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-05.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-1024x386.png" alt="" width="1024" height="386" class="aligncenter size-large wp-image-9830" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-1024x386.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-300x113.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-768x290.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-1170x441.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-05-585x221.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-05.png 1228w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
This will open a new tab with an XML file. Save the file as <em>metadata.xml</em>.</p>
<h1>AWS</h1>
<p>Go to console.aws.amazon.com and log with your root account. Go to <strong>IAM Identity Center</strong> and click on <strong>Enable</strong> and then select  <strong>Enable with AWS Organizations</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-06.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-06.png" alt="" width="594" height="357" class="aligncenter size-full wp-image-9831" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-06.png 594w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-06-300x180.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-06-585x352.png 585w" sizes="(max-width: 594px) 100vw, 594px" /></a><br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-07.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-1024x789.png" alt="" width="1024" height="789" class="aligncenter size-large wp-image-9832" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-1024x789.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-300x231.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-768x591.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-1170x901.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-07-585x450.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-07.png 1309w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Settings </strong>on the left, then <strong>Actions </strong>drop-down and click on <strong>Customize AWS access portal URL</strong>. Change the URL so it&#8217;s something that you can easily remember, e.g. yourorg or aws-master.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-10.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-1024x354.png" alt="" width="1024" height="354" class="aligncenter size-large wp-image-9836" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-1024x354.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-300x104.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-768x266.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-1536x532.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-2048x709.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-1920x664.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-1170x405.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-10-585x202.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Settings </strong>on the left, then <strong>Actions </strong>drop-down and click on <strong>Change identity source</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-08.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-1024x320.png" alt="" width="1024" height="320" class="aligncenter size-large wp-image-9833" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-1024x320.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-300x94.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-768x240.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-1536x481.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-2048x641.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-1920x601.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-1170x366.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-08-585x183.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose <strong>External identity provider</strong> and click <strong>Next</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-09.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-1024x579.png" alt="" width="1024" height="579" class="aligncenter size-large wp-image-9834" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-1024x579.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-300x170.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-768x434.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-1170x663.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-09-585x331.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-09.png 1291w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
You&#8217;ll land on a page with two parts, <em>Service provider metadata</em> (that&#8217;s AWS) and <em>Identity provider metadata</em> (that&#8217;s Okta).<br />
Click on <strong>Download metadata file</strong> from upper right. You&#8217;ll need this in Okta. The file will be named with some date prefix and some characters plus metadata.xml<br />
Copy the values for the 2nd and 3rd entry. You&#8217;ll also need these for Okta.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-11.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-11-1024x296.png" alt="" width="1024" height="296" class="aligncenter size-large wp-image-9838" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-11-1024x296.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-11-300x87.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-11-768x222.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-11-585x169.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-11.png 1155w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Choose file</strong> under <em>Identity provider metadata</em> and upload the <em>metadata.xml</em> file that you&#8217;ve downloaded earlier from Okta.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-12.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-12.png" alt="" width="649" height="319" class="aligncenter size-full wp-image-9839" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-12.png 649w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-12-300x147.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-12-585x288.png 585w" sizes="(max-width: 649px) 100vw, 649px" /></a><br />
Click <strong>Next</strong>, type <strong>ACCEPT </strong>and finally click <strong>Change identity source</strong>.</p>
<h1>Okta</h1>
<p>Go back to Okta, <strong>Sign On</strong> tab and right bellow click <strong>Edit</strong>. Enter the two URL entries that you copied from AWS. Click <strong>Save </strong>after.</p>
<h1>AWS</h1>
<p>Go back to AWS, then <strong>IAM Identity Center</strong>, click on <strong>Settings </strong>and click to <strong>Enable </strong>the <em>Automatic Provisioning</em>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-14.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-1024x144.png" alt="" width="1024" height="144" class="aligncenter size-large wp-image-9841" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-1024x144.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-300x42.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-768x108.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-1536x216.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-2048x287.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-1920x269.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-1170x164.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-14-585x82.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on <strong>Show Token</strong> and copy both values for the <strong>SCIM endpoint </strong>and the <strong>Access Token</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-15.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-1024x632.png" alt="" width="1024" height="632" class="aligncenter size-large wp-image-9842" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-1024x632.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-300x185.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-768x474.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-1170x722.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-15-585x361.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-15.png 1216w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Okta</h1>
<p>Go back to Okta and for the AWS app, click on <strong>Provisioning </strong>and then click on <strong>Configure API Integration</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-16.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-1024x759.png" alt="" width="1024" height="759" class="aligncenter size-large wp-image-9843" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-1024x759.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-300x222.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-768x569.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-1170x867.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-16-585x434.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-16.png 1345w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click <strong>Enable API Integration</strong> and enter the SCIM endpoint and the token value in the corresponding fields. Click <strong>Save</strong>. Remove the trailing slash from the end of the Base URL (SCIM endpoint) if you get an error (<em>Base URL: Does not match required pattern</em>)<br />
Under <strong>Provisioning </strong>tab, click on <strong>To App</strong>, click <strong>Edit </strong>and select all three checkmarks. Click <strong>Save</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-18.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-1024x755.png" alt="" width="1024" height="755" class="aligncenter size-large wp-image-9844" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-1024x755.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-300x221.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-768x567.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-1536x1133.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-1170x863.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18-585x432.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-18.png 1575w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Then, go to <strong>Assignments </strong>tab and assign a group or a user to this AWS app.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-19.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-1024x441.png" alt="" width="1024" height="441" class="aligncenter size-large wp-image-9846" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-1024x441.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-300x129.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-768x331.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-1170x504.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-19-585x252.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-19.png 1222w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>AWS</h1>
<p>Go back to AWS and under <strong>IAM Identity Center</strong> and then <strong>Users</strong>, you can see the user that we just assigned.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-20.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-1024x337.png" alt="" width="1024" height="337" class="aligncenter size-large wp-image-9847" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-1024x337.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-300x99.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-768x253.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-1170x385.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-20-585x193.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-20.png 1279w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
But this user has no rights in AWS, so we have to create a Permission set and then assign this user to that permission set and the account that this user has access to. Click on <strong>Permission sets</strong> on the left and then <strong>Create permission set</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-21.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-21-1024x381.png" alt="" width="1024" height="381" class="aligncenter size-large wp-image-9848" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-21-1024x381.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-21-300x112.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-21-768x286.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-21-585x218.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-21.png 1050w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
I chose <strong>AdministratorAccess </strong>from the <strong>Predefined permission sets</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-22.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-22.png" alt="" width="657" height="616" class="aligncenter size-full wp-image-9849" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-22.png 657w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-22-300x281.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-22-585x548.png 585w" sizes="(max-width: 657px) 100vw, 657px" /></a><br />
Go back to <strong>AWS accounts</strong>, click on the account and choose <strong>Assign users or groups</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-23.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-1024x380.png" alt="" width="1024" height="380" class="aligncenter size-large wp-image-9850" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-1024x380.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-300x111.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-768x285.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-1536x570.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-1170x434.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23-585x217.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-23.png 1642w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Select the <strong>User</strong> tab, click on the user, click <strong>Next</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-24.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-1024x641.png" alt="" width="1024" height="641" class="aligncenter size-large wp-image-9851" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-1024x641.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-300x188.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-768x481.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-1170x732.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-24-585x366.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-24.png 1270w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Select the permission set that we just created and click <strong>Next </strong>then <strong>Submit</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-25.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-25.png" alt="" width="766" height="582" class="aligncenter size-full wp-image-9852" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-25.png 766w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-25-300x228.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-25-585x444.png 585w" sizes="(max-width: 766px) 100vw, 766px" /></a><br />
We just told AWS that the user xyz will have access to the account abc with AdministratorAccess permissions.</p>
<h1>Test SSO</h1>
<p>Open a new tab and go to the URL that you configured earlier (<em>Customize AWS access portal</em>). It should be https://whatever.awsapps.com/start.<br />
AWS will redirect you to Okta where you&#8217;ll enter your Okta username.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-26.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-26.png" alt="" width="693" height="886" class="aligncenter size-full wp-image-9853" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-26.png 693w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-26-235x300.png 235w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-26-585x748.png 585w" sizes="(max-width: 693px) 100vw, 693px" /></a><br />
Once authenticated, you&#8217;ll see the AWS accounts in your org and the permissions that you have.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P170-27.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P170-27.png" alt="" width="904" height="577" class="aligncenter size-full wp-image-9854" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P170-27.png 904w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-27-300x191.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-27-768x490.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P170-27-585x373.png 585w" sizes="(max-width: 904px) 100vw, 904px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/03/okta-and-aws-sso-aws-iam-identity-center/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Entra ID (Azure AD) and AWS SSO (AWS IAM Identity Center)</title>
		<link>https://blog.andreev.it/2024/03/entra-id-azure-ad-and-aws-sso/</link>
					<comments>https://blog.andreev.it/2024/03/entra-id-azure-ad-and-aws-sso/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 11 Mar 2024 18:58:39 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[AWS IAM Identity Center]]></category>
		<category><![CDATA[Azure AD]]></category>
		<category><![CDATA[entra ID]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9787</guid>

					<description><![CDATA[I wrote about this topic a couple of years ago, but some things changed&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>I wrote about this <a href="https://blog.andreev.it/2021/06/azure-aws-use-azure-ad-with-aws-sso/" rel="noopener" target="_blank">topic </a>a couple of years ago, but some things changed so I am writing this post again. In this post I&#8217;ll explain how to log to AWS Console and AWS cli with Entra ID (former Azure AD) credentials. For this you&#8217;ll need admin access to both the AWS account and Entra ID. </p>
<h1>IAM Identity Center</h1>
<p>The former AWS Single Sign-on is now IAM Identity Center. You need to enable this in order to configure SSO. Go to <strong>IAM Identity Center</strong> and click on the <strong>Enable</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-1024x277.png" alt="" width="1024" height="277" class="aligncenter size-large wp-image-9788" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-1024x277.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-300x81.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-768x208.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-1536x415.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-1170x316.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01-585x158.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-01.png 1887w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Enable it with AWS Organizations.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-02.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-1024x828.png" alt="" width="1024" height="828" class="aligncenter size-large wp-image-9789" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-1024x828.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-300x243.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-768x621.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-1170x947.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-02-585x473.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-02.png 1246w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
What you want to do first is to change the AWS access portal URL. Click on the <strong>Edit </strong>button and change the URL. It has to be unique, so don&#8217;t expect you can type aws and be done with it. </p>
<p><a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1.png" alt="" width="796" height="823" class="aligncenter size-full wp-image-9821" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1.png 796w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1-290x300.png 290w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1-768x794.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-03-1-585x605.png 585w" sizes="(max-width: 796px) 100vw, 796px" /></a></p>
<p>Another setting that you have to change is the <strong>Identity source</strong>. On the left side of the <strong>IAM Identity Center</strong>, you are on the <strong>Dashboard </strong>screen. Click on <strong>Settings </strong>and then click on <strong>Change identity source</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-1024x450.png" alt="" width="1024" height="450" class="aligncenter size-large wp-image-9791" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-1024x450.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-300x132.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-768x337.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-1536x675.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-2048x900.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-1920x844.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-1170x514.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-04-585x257.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose <strong>External identity provider</strong> and click <strong>Next</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-05.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-1024x539.png" alt="" width="1024" height="539" class="aligncenter size-large wp-image-9792" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-1024x539.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-300x158.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-768x404.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-1170x616.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-05-585x308.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-05.png 1305w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
You will see this screen which consists of two parts, service provider metatada (that&#8217;s AWS) and Identity provider metadata (that&#8217;s Entra ID). Click on <strong>Download metadata file</strong>. You&#8217;ll get an XML file.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-06.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-906x1024.png" alt="" width="906" height="1024" class="aligncenter size-large wp-image-9793" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-906x1024.png 906w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-265x300.png 265w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-768x868.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-1170x1323.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-06-585x661.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-06.png 1239w" sizes="(max-width: 906px) 100vw, 906px" /></a><br />
Keep the page open and in a new browser window go to portal.azure.com.</p>
<h1>Entra ID</h1>
<p>Once logged to Azure, go to <strong>Enterprise Applications</strong> and search for <strong>AWS IAM Identity Center (successor to AWS Single Sign-On)</strong>. Click to install. When you are presented with the <strong>Overview </strong>of the application, click on <strong>Assign users and groups</strong>. Depending on your Entra ID level (P1 or P2) you might not be able to assign groups, so you have to assign users.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-07.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-1024x600.png" alt="" width="1024" height="600" class="aligncenter size-large wp-image-9795" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-1024x600.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-300x176.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-768x450.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-1536x900.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-2048x1199.png 2048w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-1920x1124.png 1920w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-1170x685.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-07-585x343.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
The next step is to setup the SSO. Click on <strong>2. Set up single sign on</strong> and then <strong>SAML</strong>. You&#8217;ll be presented with this page and eventually you&#8217;ll see a prompt if you want to save some SSO settings. If you don&#8217;t see the prompt, no worries. If you see the prompt, click <strong>Yes</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-08.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-814x1024.png" alt="" width="814" height="1024" class="aligncenter size-large wp-image-9798" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-814x1024.png 814w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-238x300.png 238w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-768x967.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-1170x1473.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-08-585x736.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-08.png 1218w" sizes="(max-width: 814px) 100vw, 814px" /></a><br />
Click on <strong>Upload metadata file</strong> and point to the XML file that you saved from AWS. Click <strong>Save </strong>and don&#8217;t worry about any of the fields there. If you see a popup to test the config, just click <strong>No, I&#8217;ll test later</strong>. Under step 3, <strong>SAML Certificates</strong>, click to <strong>Download </strong>Federation Metadata XML.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-09.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-09.png" alt="" width="1009" height="546" class="aligncenter size-full wp-image-9799" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-09.png 1009w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-09-300x162.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-09-768x416.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-09-585x317.png 585w" sizes="(max-width: 1009px) 100vw, 1009px" /></a><br />
You&#8217;ll get another XML file called <strong>AWS IAM Identity Center (successor to AWS Single Sign-On).xml</strong>.</p>
<h1>Back to AWS</h1>
<p>Go back to the same page where you&#8217;ve downloaded the XML metadata file, but this time look at the bottom part where it says <strong>Identity provider metadata</strong> and <strong>IdP SAML metadata</strong>. Click on <strong>Choose file</strong> and upload the file from Azure. Click <strong>Next</strong>, type <strong>ACCEPT </strong>and click <strong>Change identity source</strong>.<br />
Once completed click to <strong>Enable </strong>Automatic provisioning.  Copy the <strong>SCIM endpoint</strong> and the <strong>access token</strong>. </p>
<h1>Back to Azure</h1>
<p>Under the AWS SSO application. on the left side click on <strong>Provisioning</strong>. Change the <strong>Provisioning Mode</strong> to <strong>Automatic</strong>. Expand <strong>Admin credentials</strong> and for <strong>Tenant ID</strong> enter the <strong>SCIM endpoint</strong> and for <strong>Secret Token</strong>, enter the token. Click <strong>Test Connection</strong> and upon successful test, click <strong>Save</strong>. If you see clientsecret instead of Tenant ID, it means you are not using the right AWS application and you use the AWS Single Account application instead. Start from scratch.<br />
Anytime you assign a new user or a group to AWS application, Azure will sync the user to AWS. But, instead of waiting (40 mins by default) for the initial sync, click on the <strong>Overview </strong>and click <strong>Start provisioning</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-10.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-1024x704.png" alt="" width="1024" height="704" class="aligncenter size-large wp-image-9803" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-1024x704.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-300x206.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-768x528.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-1170x804.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-10-585x402.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-10.png 1200w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Back to AWS</h1>
<p>If your provisioning works fine, you&#8217;ll see your assigned Azure user under <strong>Users </strong>in IAM Identity Center. On the left side of the IAM Identity Center, click on <strong>Permission sets</strong>. Click <strong>Create Permission set</strong> and choose <strong>Predefined permission set</strong>, then <strong>AdministratorAccess</strong>. Change the name, description, session duration if needed and then click <strong>Next</strong> and then <strong>Create</strong>.<br />
Finally, click on <strong>AWS accounts</strong>, select an account from the organization, click <strong>Assign users or groups</strong>, click <strong>Users </strong>or <strong>Groups</strong>, select a user and click <strong>Next</strong>. Assign a permission set, click <strong>Next </strong>and <strong>Submit</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-11.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-11-1024x675.png" alt="" width="1024" height="675" class="aligncenter size-large wp-image-9804" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-11-1024x675.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-11-300x198.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-11-768x506.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-11-585x386.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-11.png 1048w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, test the SSO by going to whatever.awsapps.com/start# and you&#8217;ll be redirected to the Microsoft sign in prompt. Follow the authentication process for Entra ID (username, password, MFA etc) and once you pass the authentication process you&#8217;ll see the AWS account that you have access to.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-12.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-1024x309.png" alt="" width="1024" height="309" class="aligncenter size-large wp-image-9806" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-1024x309.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-300x91.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-768x232.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-1170x353.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-12-585x177.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-12.png 1513w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>AWS CLI and SSO</h1>
<p>Follow the instructions to <a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener" target="_blank">install </a>AWS CLI for your OS, then configure SSO. Name your session, enter the URL you configured earlier, enter the region where you configured SSO and hit enter for the registration scopes. </p>
<pre class="brush: bash; title: ; notranslate">
aws configure sso
SSO session name (Recommended): sso
SSO start URL &#x5B;None]: https://your_url.awsapps.com/start
SSO region &#x5B;None]: us-east-1
SSO registration scopes &#x5B;sso:account:access]:
Attempting to automatically open the SSO authorization page in your default browser.
If the browser does not open or you wish to use a different device to authorize this request, open the following URL:

https://device.sso.us-east-1.amazonaws.com/

Then enter the code:

PWKG-CAFZ
</pre>
<p>After successful authentication, you&#8217;ll be presented with the accounts available to you. Pick one.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/03/P171-13.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-1024x305.png" alt="" width="1024" height="305" class="aligncenter size-large wp-image-9819" srcset="https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-1024x305.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-300x89.png 300w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-768x229.png 768w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-1536x458.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-1170x349.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13-585x174.png 585w, https://blog.andreev.it/wp-content/uploads/2024/03/P171-13.png 1734w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
You&#8217;ll be prompted to choose the roles available to you and then specify your default region, default output and the profile. \<br />
NOTE: If you&#8217;ll be working with multiple accounts, maybe you should specify a different profile than the default. It&#8217;s up to you. E.g. specify prod and dev profiles. </p>
<pre class="brush: plain; title: ; notranslate">
There are 2 AWS accounts available to you.
Using the account ID 123456789012
The only role available to you is: AdministratorAccess
Using the role name &quot;AdministratorAccess&quot;
CLI default client Region &#x5B;None]: us-east-2
CLI default output format &#x5B;None]: json
CLI profile name &#x5B;AdministratorAccess-123456789012]: default

To use this profile, specify the profile name using --profile, as shown:

aws s3 ls --profile default
</pre>
<p>After your SSO session expires, you have to log back in.</p>
<pre class="brush: bash; title: ; notranslate">
aws sso login
</pre>
<p>If you are like me and always uses the <strong>default </strong>profile so I don&#8217;t have to type <em>&#8211;profile</em> parameter, then anytime you want to use a different account, just <strong>aws configure sso</strong> again, but specify the other account now. </p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/03/entra-id-azure-ad-and-aws-sso/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: Get secrets from Secrets Manager using CSI Driver and ASCP</title>
		<link>https://blog.andreev.it/2024/01/aws-get-secrets-from-secrets-manager-using-csi-driver-and-ascp/</link>
					<comments>https://blog.andreev.it/2024/01/aws-get-secrets-from-secrets-manager-using-csi-driver-and-ascp/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Wed, 03 Jan 2024 21:02:53 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[ascp]]></category>
		<category><![CDATA[secrets manager]]></category>
		<category><![CDATA[secrets store csi driver]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9718</guid>

					<description><![CDATA[In one of my previous posts, I wrote about accessing DynamoDB and AWS Gateway/Lambda&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In one of my previous <a href="https://blog.andreev.it/2023/09/aws-access-dynamodb-and-secrets-manager-from-node-js-on-ec2-and-eks/" rel="noopener" target="_blank">posts</a>, I wrote about accessing DynamoDB and AWS Gateway/Lambda using IAM roles and secrets from EC2/EKS Node.js application. The idea is to reference passwords in your code and never store and secrets/passwords in the code. By using roles, we can access the secrets from the AWS Secrets Manager and use them dynamically in the code. However, that solution has its own pros and cons. The pros are that you don&#8217;t have to do anything extra except creating an IAM role and secrets. The cons are that you need AWS SDK as part of your application. You will use AWS SDK for Node.js to get the credentials from the Secrets Manager. This means you have to maintain up-to-date AWS SDK and add extra code. Another way is to use the k8s CSI driver and ASCP (provider) that pretty much gets the secret and mounts it as a file. You still need IAM roles to access the Secrets Manager but you don&#8217;t need AWS SDK. You can read the secrets as you read a file. </p>
<h1>Create an EKS cluster</h1>
<p>We&#8217;ll create an EKS cluster using the <strong>eksctl</strong> tool.</p>
<pre class="brush: bash; title: ; notranslate">
CLUSTER_NAME=&quot;eksDemoCluster&quot;
REGION=&quot;us-east-2&quot;
NAMESPACE=&quot;default&quot;

eksctl create cluster --name $CLUSTER_NAME \
  --region $REGION --version 1.28 \
  --node-type t3.small --nodes 2
</pre>
<p>We&#8217;ll use helm to install the CSI driver and the specific ASCP provider for AWS.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm repo add aws-secrets-manager https://aws.github.io/secrets-store-csi-driver-provider-aws
helm repo update
helm install -n kube-system csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver
helm install -n kube-system secrets-provider-aws aws-secrets-manager/secrets-store-csi-driver-provider-aws
</pre>
<p>Check if everything is OK.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl --namespace=kube-system get pods -l &quot;app=secrets-store-csi-driver&quot;
NAME                                               READY   STATUS    RESTARTS   AGE
csi-secrets-store-secrets-store-csi-driver-nhq7b   3/3     Running   0          19s
csi-secrets-store-secrets-store-csi-driver-tft6q   3/3     Running   0          19s
</pre>
<h1>Create a secret</h1>
<p>Let&#8217;s create a secret that we&#8217;ll be using in a mock-up app.</p>
<pre class="brush: bash; highlight: [1,2,3,4,5]; title: ; notranslate">
SECRET_NAME=&quot;demoapp/mysecret&quot;
SECRET_ARN=$(aws secretsmanager create-secret  --name $SECRET_NAME \
  --secret-string &#039;{&quot;username&quot;:&quot;Administrator&quot;, &quot;password&quot;:&quot;SuperSecretPassword$&quot;}&#039; \
  --output text --query &#039;ARN&#039; --region $REGION)
echo $SECRET_ARN
arn:aws:secretsmanager:us-east-2:123456789012:secret:demoapp/mysecret-lboyfd
</pre>
<h1>Create an IAM policy</h1>
<p>We also need to define an IAM policy that will allow our EKS pods to access only this particular secret and nothing else.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; polDemoSecret.json
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B; {
        &quot;Effect&quot;: &quot;Allow&quot;,
        &quot;Action&quot;: &#x5B;&quot;secretsmanager:GetSecretValue&quot;, &quot;secretsmanager:DescribeSecret&quot;],
        &quot;Resource&quot;: &#x5B;&quot;$SECRET_ARN&quot;]
    } ]
}
EOF
</pre>
<p>Create the policy.</p>
<pre class="brush: bash; highlight: [1,2,3,4]; title: ; notranslate">
POLICY_ARN=$(aws iam create-policy --policy-name polDemoSecret \
  --policy-document file://polDemoSecret.json \
  --output text --query &#039;Policy.Arn&#039;)
echo $POLICY_ARN
arn:aws:iam::123456789012:policy/polDemoSecret
</pre>
<h1>Create an IAM OIDC provider</h1>
<p>Let’s determine whether we have an existing IAM OIDC provider for our cluster.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
aws eks describe-cluster --name $CLUSTER_NAME --region $REGION \
  --query &quot;cluster.identity.oidc.issuer&quot; --output text
https://oidc.eks.us-east-2.amazonaws.com/id/D7FAD6F2BEF430FC1CB673777A9E4FED
</pre>
<p>When we provisioned the cluster with eksctl, the OIDC was created automatically. We need the ID of the OIDC which is the hex value at the end.</p>
<pre class="brush: bash; highlight: [1,2,3]; title: ; notranslate">
OIDCID=$(aws eks describe-cluster --name $CLUSTER_NAME --region $REGION \
  --query &quot;cluster.identity.oidc.issuer&quot; --output text | cut -d &#039;/&#039; -f 5)
echo $OIDCID
E9B5E80C37A71FA3932B94EFF5BA3437
</pre>
<p>Check if the IAM OIDC provider is already configured. It shouldn’t if you provisioned a brand new cluster.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam list-open-id-connect-providers | grep $OIDCID 
</pre>
<p>If for whatever reason, you had an output from the command above, skip this <em>associate-iam-oidc-provider</em> step below.<br />
If you don&#8217;t have any output, associate the OIDC provider with the cluster.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --region $REGION --approve
2023-12-22 12:27:47 &#x5B;ℹ]  will create IAM Open ID Connect provider for cluster &quot;eksDemoCluster&quot; in &quot;us-east-2&quot;
2023-12-22 12:27:47 &#x5B;✔]  created IAM Open ID Connect provider for cluster &quot;eksDemoCluster&quot; in &quot;us-east-2&quot;
</pre>
<h1>Cluster service account</h1>
<p>We also need a service account for the cluster and we need to attache the policy for the secrets that we created before.</p>
<pre class="brush: bash; title: ; notranslate">
SA_NAME=&quot;sademosecret&quot;
eksctl create iamserviceaccount --name $SA_NAME --namespace $NAMESPACE \
--cluster $CLUSTER_NAME  --region $REGION \
--attach-policy-arn $POLICY_ARN --approve --override-existing-serviceaccounts
kubectl get sa
</pre>
<h1>Service Provider Class</h1>
<p>&#8230;and finally we need a service provider class that we&#8217;ll deploy on the EKS cluster.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; spc.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: spc-demosecret
  namespace: $NAMESPACE
spec:
  provider: aws
  parameters:
    objects: |
        - objectName: &quot;$SECRET_NAME&quot;
          objectType: &quot;secretsmanager&quot;
EOF
kubectl apply -f spc.yaml
</pre>
<h1>Test with busybox pod</h1>
<p>Create this YAML file that will deploy a pod. Pay attention to highlighted lines. We have to provide the namespace, the service account and the service provider class that specifies the secret.Check this <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver.html" rel="noopener" target="_blank">link</a> on how to mount and specify different secrets.</p>
<pre class="brush: bash; highlight: [6,8,15]; title: ; notranslate">
cat &lt;&lt; EOF &gt; app.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: $NAMESPACE
spec:
  serviceAccountName: $SA_NAME
  volumes:
  - name: secretsvolume
    csi:
       driver: secrets-store.csi.k8s.io
       readOnly: true
       volumeAttributes:
         secretProviderClass: &quot;spc-demosecret&quot;
  containers:
  - image: public.ecr.aws/docker/library/busybox:1.36
    command:
      - sleep
      - &quot;3600&quot;
    imagePullPolicy: IfNotPresent
    name: busybox
    volumeMounts:
    - name: secretsvolume
      mountPath: &quot;/mnt/secrets-store&quot;
      readOnly: true
  restartPolicy: Always
EOF
kubectl apply -f app.yaml
</pre>
<p>Test the secrets retrieval. As you can see, the secret that we created was <strong>demoapp/mysecret</strong>, but in this case the forward slash would mean a directory, so the actual secret is <strong>demoapp_mysecret</strong>&#8230; and it&#8217;s simple as that. The secret is just a file under the <em>/mnt/secrets-store</em> directory.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl exec -it $(kubectl get pods | awk &#039;/busybox/{print $1}&#039; | head -1) -- cat /mnt/secrets-store/demoapp_mysecret; echo
{&quot;username&quot;:&quot;Administrator&quot;, &quot;password&quot;:&quot;SuperSecretPassword$&quot;}
</pre>
<h1>Node.js app</h1>
<p>Create a folder and add this file. Save it as <strong>server.js</strong>.</p>
<pre class="brush: bash; highlight: [8]; title: ; notranslate">
&#039;use strict&#039;;
const express = require(&#039;express&#039;)
const app = express()
const port = 3000
 
var fs = require(&#039;fs&#039;);
try {  
    var data = fs.readFileSync(&#039;/mnt/secrets-store/demoapp_mysecret&#039;, &#039;utf8&#039;);
    console.log(data.toString());    
} catch(e) {
    console.log(&#039;Error:&#039;, e.stack);
}

app.get(&#039;/&#039;, (req, res) =&gt; {
    res.send(JSON.stringify(data));
})
 
app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})
</pre>
<p>Let&#8217;s create the <strong>Dockerfile</strong> so we can build the image.</p>
<pre class="brush: bash; title: ; notranslate">
FROM node:20
  
# Create app directory
WORKDIR /usr/src/app
  
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
  
RUN npm install
# If you are building your code for production
# RUN npm ci --only=production
  
# Bundle app source
COPY . .
  
EXPOSE 3000
CMD &#x5B; &quot;node&quot;, &quot;server.js&quot; ]
</pre>
<p>Create a <strong>.dockerignore</strong> file.</p>
<pre class="brush: bash; title: ; notranslate">
node_modules
npm-debug.log
</pre>
<p>Now, we can create the image and push to Dockerhub or some other image repo. Use your own repo here at line 1.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
sudo docker build . -t klimenta/secretsdemo
sudo docker images
sudo docker push klimenta/secretsdemo
</pre>
<p>Create a file to deploy 2 replicas and a load balancer. Save it as something.yaml and deploy it. Analyze the file and see how we use the service account, secret provider class and the image.</p>
<pre class="brush: bash; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demosecret
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      run: demosecret
  template:
    metadata:
      labels:
        run: demosecret
    spec:
      serviceAccountName: sademosecret
      volumes:
      - name: secretsvolume
        csi:
          driver: secrets-store.csi.k8s.io
          readOnly: true
          volumeAttributes:
            secretProviderClass: &quot;spc-demosecret&quot;
      containers:
      - name: demosecret
        image: klimenta/secretsdemo
        volumeMounts:
        - name: secretsvolume
          mountPath: &quot;/mnt/secrets-store&quot;
          readOnly: true
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
  type: LoadBalancer
  selector:
    run: demosecret
</pre>
<p>Once you deploy with <em>kubectl apply -f something.yaml</em>, type <em>kubectl get svc</em> and you&#8217;ll see a public URL.<br />
If you go to that URL, you&#8217;ll see our secret.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2024/01/P169-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-1024x123.png" alt="" width="1024" height="123" class="aligncenter size-large wp-image-9746" srcset="https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-1024x123.png 1024w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-300x36.png 300w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-768x92.png 768w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-1536x185.png 1536w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-1170x141.png 1170w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01-585x70.png 585w, https://blog.andreev.it/wp-content/uploads/2024/01/P169-01.png 1812w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2024/01/aws-get-secrets-from-secrets-manager-using-csi-driver-and-ascp/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Install ESXi 8 on Intel NUC 13</title>
		<link>https://blog.andreev.it/2023/12/install-esxi-8-on-intel-nuc-13/</link>
					<comments>https://blog.andreev.it/2023/12/install-esxi-8-on-intel-nuc-13/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 18 Dec 2023 14:42:16 +0000</pubDate>
				<category><![CDATA[ESX/ESXi]]></category>
		<category><![CDATA[ESXi]]></category>
		<category><![CDATA[home lab]]></category>
		<category><![CDATA[Intel]]></category>
		<category><![CDATA[NUC]]></category>
		<category><![CDATA[vCenter]]></category>
		<category><![CDATA[VMware]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9639</guid>

					<description><![CDATA[Recently I was playing with Kubernetes and racked a substantial bill in AWS and&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Recently I was playing with Kubernetes and racked a substantial bill in AWS and Azure so I decided to bring the lab home. I needed a small but powerful PC that can run multiple test VMs and Kubernetes clusters. Kubernetes on a bare metal is different than EKS or AKS, especially when it comes to handling load balancers, ingress, IAM roles etc. Some of these things don&#8217;t exist on a bare metal machine.<br />
I bought a <a href="https://www.amazon.com/dp/B0C1SHV6RS?psc=1&#038;ref=ppx_yo2ov_dt_b_product_details" rel="noopener" target="_blank">NUC 13 small factor PC</a> from Amazon and added <a href="https://www.amazon.com/gp/product/B08RK2SR23/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&#038;psc=1" rel="noopener" target="_blank">Samsung NVMe 2TB SSD</a> and <a href="https://www.amazon.com/gp/product/B07ZLCVKPV/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&#038;psc=1" rel="noopener" target="_blank">Crucial 64GB RAM</a>. After 2 months (these things are expensive!!!), I bought a <a href="https://www.amazon.com/gp/product/B0BRN9J1JN/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&#038;psc=1" rel="noopener" target="_blank">Synology NAS </a>with <a href="https://www.amazon.com/dp/B084ZV4DXB?psc=1&#038;ref=ppx_yo2ov_dt_b_product_details" rel="noopener" target="_blank">2 x 8GB SATA disks</a>. Next, is to upgrade the NAS with SSD that will act as cache, but I am not in a rush. Nevertheless&#8230;</p>
<h1>Initial SSD, RAM setup</h1>
<p>First thing you need to do is to CAREFULLY open the PC and make sure you don&#8217;t pull too hard and break the ribbon. Good thing is that the screws are not coming off and there is a spring mechanism so it&#8217;s super easy to open and close the bottom.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-scaled.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1024x768.jpg" alt="" width="1024" height="768" class="aligncenter size-large wp-image-9645" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1024x768.jpg 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-300x225.jpg 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-768x576.jpg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1536x1152.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-2048x1536.jpg 2048w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1200x900.jpg 1200w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1920x1440.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-1170x878.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-01-585x439.jpg 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Add the NVMe SSD on the left side, but remove the screw first, bottom left. You&#8217;ll use it to tighten the SSD after.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-scaled.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1024x768.jpg" alt="" width="1024" height="768" class="aligncenter size-large wp-image-9646" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1024x768.jpg 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-300x225.jpg 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-768x576.jpg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1536x1152.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-2048x1536.jpg 2048w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1200x900.jpg 1200w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1920x1440.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-1170x878.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-02-585x439.jpg 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Add the memory on the right side, you can add up to 64GB RAM hence 2 x 32GB modules. If you boot the PC even with a bootable USB stick, you&#8217;ll get an error saying <strong>&#8220;A bootable device has not been detected&#8221;</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-1024x242.png" alt="" width="1024" height="242" class="aligncenter size-large wp-image-9648" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-1024x242.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-300x71.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-768x182.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-1170x277.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-03-585x138.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-03.png 1200w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Hit <strong>CTRL-ALT-DEL </strong>and then <strong>F2</strong> to enter BIOS. From there, go to <strong>Boot</strong> menu, then <strong>Boot Priority</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-1024x762.png" alt="" width="1024" height="762" class="aligncenter size-large wp-image-9650" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-1024x762.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-300x223.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-768x571.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-1170x871.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-04-585x435.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-04.png 1266w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Check <strong>Boot USB Devices First</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-05.jpeg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-768x1024.jpeg" alt="" width="768" height="1024" class="aligncenter size-large wp-image-9652" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-768x1024.jpeg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-225x300.jpeg 225w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-1152x1536.jpeg 1152w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-1536x2048.jpeg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-1170x1560.jpeg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05-585x780.jpeg 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-05.jpeg 1645w" sizes="(max-width: 768px) 100vw, 768px" /></a><br />
If you boot from ESXi 8 ISO now, most likely you&#8217;ll get an <strong>Fatal CPU mismatch on feature</strong> PSOD error. So, while still in the BIOS, go to <strong>Power, Performance and Cooling </strong>tab. Expand the first item <strong>External Ambient Temperature Tolerance</strong> and change the temperature setting to <strong>User Defined.</strong><br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-06.jpeg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-768x1024.jpeg" alt="" width="768" height="1024" class="aligncenter size-large wp-image-9653" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-768x1024.jpeg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-225x300.jpeg 225w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-1152x1536.jpeg 1152w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-1536x2048.jpeg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-1170x1560.jpeg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06-585x780.jpeg 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-06.jpeg 1645w" sizes="(max-width: 768px) 100vw, 768px" /></a><br />
Now, you can enter the <strong>Performance</strong> menu which was grayed if the setting is not <strong>User Defined</strong> for the <strong>External Ambient Temperature Tolerance</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-scaled.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-768x1024.jpg" alt="" width="768" height="1024" class="aligncenter size-large wp-image-9654" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-768x1024.jpg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-225x300.jpg 225w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-1152x1536.jpg 1152w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-1536x2048.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-scaled.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-1170x1560.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-07-585x780.jpg 585w" sizes="(max-width: 768px) 100vw, 768px" /></a><br />
Once there, go to <strong>Processor</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-scaled.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-768x1024.jpg" alt="" width="768" height="1024" class="aligncenter size-large wp-image-9655" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-768x1024.jpg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-225x300.jpg 225w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-1152x1536.jpg 1152w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-1536x2048.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-scaled.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-1170x1560.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-08-585x780.jpg 585w" sizes="(max-width: 768px) 100vw, 768px" /></a><br />
Once there, change the number of <strong>Active Efficient Cores</strong> to 0.<br />
In addition, when you install ESXi 8 and when you access the GUI, you&#8217;ll see a nagging warning that TPM cannot be contacted or something similar. No matter how many times you acknowledge this warning it keeps coming back. So, while you in the BIOS, go to <strong>Security </strong>tab, then <strong>Security Features</strong> and uncheck <strong>Intel Platform Trust Technology</strong>.<br />
Save the settings with <strong>F10</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-scaled.jpg" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-768x1024.jpg" alt="" width="768" height="1024" class="aligncenter size-large wp-image-9656" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-768x1024.jpg 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-225x300.jpg 225w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-1152x1536.jpg 1152w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-1536x2048.jpg 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-scaled.jpg 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-1170x1560.jpg 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-09-585x780.jpg 585w" sizes="(max-width: 768px) 100vw, 768px" /></a></p>
<h1>Install ESXi 8</h1>
<p>At this point, reboot and boot from ESXi ISO USB. Check this guy for more info (<a href="https://williamlam.com/2023/03/esxi-on-intel-nuc-13-pro-arena-canyon.html" rel="noopener" target="_blank">link</a> and <a href="https://williamlam.com/2023/03/how-to-disable-the-efficiency-cores-e-cores-on-an-intel-nuc.html" rel="noopener" target="_blank">link</a>).<br />
I have a <a href="https://www.vmug.com/membership/vmug-advantage-membership/" rel="noopener" target="_blank">VMUG Advantage</a> subscription so I can freely download all VMWare software from <a href="https://vmugadvantage.onthehub.com/WebStore/Security/Signin.aspx?rurl=%2fWebStore%2fProductsByMajorVersionList.aspx" rel="noopener" target="_blank">here</a>. As long as you pay the yearly fee, you can have the licenses and software indefinitely.<br />
Choose where to install the ESXi (Samsung SSD).<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-10.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-1024x447.png" alt="" width="1024" height="447" class="aligncenter size-large wp-image-9657" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-1024x447.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-300x131.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-768x335.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-1536x671.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-2048x895.png 2048w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-1920x839.png 1920w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-1170x511.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-10-585x256.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose a root password.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-11.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-1024x345.png" alt="" width="1024" height="345" class="aligncenter size-large wp-image-9658" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-1024x345.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-300x101.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-768x258.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-1536x517.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-1170x394.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11-585x197.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-11.png 1896w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Remove the USB stick and reboot when the installation is over and you&#8217;ll see this screen after.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-12.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-1024x559.png" alt="" width="1024" height="559" class="aligncenter size-large wp-image-9659" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-1024x559.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-300x164.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-768x419.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-1536x838.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-1170x639.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12-585x319.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-12.png 1810w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
This is my DHCP assigned address, so just change it to a static IP providing DNS and the default gateway. Then, change your FQDN as well. In my case I named my box as esxi1.homelab.local.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-13.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-1024x447.png" alt="" width="1024" height="447" class="aligncenter size-large wp-image-9660" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-1024x447.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-300x131.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-768x335.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-1536x670.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-1170x511.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13-585x255.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-13.png 1700w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
At this point you have a functioning ESXi server. Open up a browser, go to the ESXi IP and log with root and the password you specified.<br />
If you have a license, add it now by going to <strong>Manage</strong> (upper left corner), then <strong>Licensing</strong> tab and click <strong>Assign license</strong> or skip this part and use the evaluation mode.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-14.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-1024x471.png" alt="" width="1024" height="471" class="aligncenter size-large wp-image-9662" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-1024x471.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-300x138.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-768x353.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-1170x538.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-14-585x269.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-14.png 1440w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
<em>Optional</em>: Click on <strong>Storage</strong> then <strong>datastore1</strong>, <strong>Actions</strong> from the menu and rename the datastore. In my case I&#8217;ll use <strong>ESX1-LOCAL</strong>.</p>
<h1>Install vCenter 8.0 appliance</h1>
<p>Now, we can install vCenter. Mount the vCenter ISO image, go to <strong>vcsa-ui-installer</strong> folder and choose the OS (win32, mac, lin64). Run the installer and click <strong>Install </strong>icon.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-15.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-1024x707.png" alt="" width="1024" height="707" class="aligncenter size-large wp-image-9665" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-1024x707.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-300x207.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-768x530.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-1170x808.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-15-585x404.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-15.png 1525w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click <strong>Next </strong>and <strong>Accept </strong>the legal terms. Then enter the IP address or FQDN if you have your DNS configured for the ESXi server you just installed. Specify the root password too.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-16.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-1024x528.png" alt="" width="1024" height="528" class="aligncenter size-large wp-image-9666" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-1024x528.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-300x155.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-768x396.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-1170x603.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-16-585x302.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-16.png 1492w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Name the vCenter and specify the vCenter root password. This is not the password that you will use to log to vCenter. This is a password for the appliance itself. You&#8217;ll need this only if you have to troubleshoot the appliance or enable/disable SSH on the appliance.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-17.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-1024x398.png" alt="" width="1024" height="398" class="aligncenter size-large wp-image-9667" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-1024x398.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-300x117.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-768x299.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-1170x455.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-17-585x228.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-17.png 1483w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose deployment size, for me the smallest size is acceptable. After all, I plan to use this primarily for Kubernetes.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-18.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-1024x658.png" alt="" width="1024" height="658" class="aligncenter size-large wp-image-9668" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-1024x658.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-300x193.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-768x493.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-1170x752.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-18-585x376.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-18.png 1510w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose the datastore.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-19.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-1024x682.png" alt="" width="1024" height="682" class="aligncenter size-large wp-image-9669" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-1024x682.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-300x200.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-768x511.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-1170x779.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-585x389.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19-263x175.png 263w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-19.png 1528w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Configure network settings, IP, gateway, DNS, FQDN&#8230;<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-20.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-1024x809.png" alt="" width="1024" height="809" class="aligncenter size-large wp-image-9670" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-1024x809.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-300x237.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-768x607.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-1170x924.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-20-585x462.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-20.png 1513w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Confirm and click <strong>Finish</strong>. After some time, you&#8217;ll get a prompt to configure vCenter. I chose to use NTP and sync with some public NTP servers. Use whatever is closest to you.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-21.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-1024x480.png" alt="" width="1024" height="480" class="aligncenter size-large wp-image-9674" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-1024x480.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-300x141.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-768x360.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-1536x721.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-1170x549.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21-585x274.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-21.png 1543w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
I&#8217;ll create a new SSO domain and I&#8217;ll use the defaults. I just have to provide a password.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-23.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-1024x733.png" alt="" width="1024" height="733" class="aligncenter size-large wp-image-9675" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-1024x733.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-300x215.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-768x550.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-1536x1100.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-1170x838.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23-585x419.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-23.png 1543w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Once vCenter is deployed go to the FQDN or the IP from a browser. Your username is no longer root, it&#8217;s <strong>administrator@vspehere.local</strong> and your password is the one you specified when you installed vCenter SSO in the previous step. Enter the license by going to <strong>Administration | Licenses </strong>from the left.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-24.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-1024x467.png" alt="" width="1024" height="467" class="aligncenter size-large wp-image-9679" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-1024x467.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-300x137.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-768x350.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-1170x534.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-24-585x267.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-24.png 1311w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Once you enter the license from the <strong>Licenses </strong>tab, go to <strong>Assets </strong>tab and click <strong>Assign License</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-25.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-1024x625.png" alt="" width="1024" height="625" class="aligncenter size-large wp-image-9680" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-1024x625.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-300x183.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-768x468.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-1170x714.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-25-585x357.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-25.png 1441w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>Inventory </strong>from the hamburger menu on the left, right-click the vCenter IP or FQDN and choose <strong>New Datacenter</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-26.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-26.png" alt="" width="640" height="742" class="aligncenter size-full wp-image-9681" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-26.png 640w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-26-259x300.png 259w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-26-585x678.png 585w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
Then right-click on the newly created datacenter and choose <strong>New Cluster</strong>.<br />
Same thing again, right-click the cluster and choose <strong>Add Hosts</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-27.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-27.png" alt="" width="829" height="516" class="aligncenter size-full wp-image-9682" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-27.png 829w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-27-300x187.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-27-768x478.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-27-585x364.png 585w" sizes="(max-width: 829px) 100vw, 829px" /></a><br />
Specify the IP or FQDN and the root password when you installed ESXi initially.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-28.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-1024x285.png" alt="" width="1024" height="285" class="aligncenter size-large wp-image-9683" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-1024x285.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-768x214.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-1536x428.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-1170x326.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28-585x163.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-28.png 1749w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Optional: iSCSI</h1>
<p>Click on the ESXi hosts, then <strong>Configure </strong>tab, click <strong>Add Software Adapter</strong> and then <strong>Add iSCSI adapter</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-29.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-1024x391.png" alt="" width="1024" height="391" class="aligncenter size-large wp-image-9685" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-1024x391.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-300x115.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-768x294.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-1170x447.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-29-585x224.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-29.png 1444w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
In my case, I have to use <strong>static discovery</strong> so click on that tab and then <strong>Add</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-30.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-1024x493.png" alt="" width="1024" height="493" class="aligncenter size-large wp-image-9686" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-1024x493.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-300x144.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-768x370.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-1170x563.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-30-585x282.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-30.png 1230w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Enter the iSCSI IP and the target name.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-31.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-31.png" alt="" width="862" height="601" class="aligncenter size-full wp-image-9687" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-31.png 862w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-31-300x209.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-31-768x535.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-31-585x408.png 585w" sizes="(max-width: 862px) 100vw, 862px" /></a><br />
Finally, click on <strong>Rescan storage</strong>in the menu above. If you need to provide your initiator, this is where you can find it under iSCSI Name (iqn.1998&#8212;_.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/12/P168-32.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-1024x789.png" alt="" width="1024" height="789" class="aligncenter size-large wp-image-9688" srcset="https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-1024x789.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-300x231.png 300w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-768x592.png 768w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-1170x901.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-32-585x451.png 585w, https://blog.andreev.it/wp-content/uploads/2023/12/P168-32.png 1189w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
To learn how you can install Kubernetes on your NUC and how to use k8s Load Balancing see the links from my blog <a href="https://blog.andreev.it/2023/10/install-kubernetes-with-cri-o-and-cilium-on-rocky-linux-9/" rel="noopener" target="_blank">here </a>and <a href="https://blog.andreev.it/2023/10/install-metallb-on-kubernetes-cluster-running-on-vmware-vms-or-bare-metal-server/" rel="noopener" target="_blank">here</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/12/install-esxi-8-on-intel-nuc-13/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: Install EKS cluster and Ingress Controller using Terraform and GitLab using OIDC and web identity</title>
		<link>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/</link>
					<comments>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 21 Nov 2023 18:39:16 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[GitLab]]></category>
		<category><![CDATA[ingress]]></category>
		<category><![CDATA[OIDC]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9581</guid>

					<description><![CDATA[In this post, I&#8217;ll explain how to create an EKS cluster in AWS and&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post, I&#8217;ll explain how to create an EKS cluster in AWS and add an Ingress Controller. We&#8217;ll use GitLab as a repo and execute the CI/CD pipeline from there.<br />
For this you&#8217;ll need:</p>
<ul>
<li>GitLab and AWS account</li>
<li>Terraform, AWS CLI, helm installed</li>
</ul>
<h1>AWS</h1>
<p>From the IAM console, create a new Identity Provider in AWS and use <strong>https://gitlab.com</strong> for both <em>Provider URL</em> and <em>Audience</em>. For self hosted GitLab use <em>gitlab.domain.com</em> or whatever is your GitLab URL.<br />
Click on <strong>Get thumbprint</strong> before clicking on <strong>Add provider</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1020x1024.png" alt="" width="1020" height="1024" class="aligncenter size-large wp-image-9585" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1020x1024.png 1020w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-300x300.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-150x150.png 150w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-768x771.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1170x1175.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-585x587.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02.png 1288w" sizes="(max-width: 1020px) 100vw, 1020px" /></a><br />
In the IAM console, create a role. I&#8217;ll name the role <strong>rolGitLab</strong>, name it whatever you like. When you create the policy, choose <strong>Custom trust policy</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1024x647.png" alt="" width="1024" height="647" class="aligncenter size-large wp-image-9587" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1024x647.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-300x190.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-768x485.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1536x971.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1170x739.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-585x370.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04.png 1725w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, here is what you have to decide. If you want to manage the cluster from GitLab only, you can ommit lines 16-23 where I allow the IAM user called gitlab to assume this role. But if you want some IAM user to manage the cluster using aws cli or kubectl, add the ARN of that IAM user in line 20. Add <em>AdministratorAccess </em>to that gitlab user but you can fine-tune those policies later. If you want to manage the cluster from GitLab, you have to register an agent and provide a config file. You can read how to do that <a href="https://docs.gitlab.com/ee/user/clusters/agent/" rel="noopener" target="_blank">here</a>.<br />
Replace the default custom trust policy with this JSON.</p>
<pre class="brush: xml; highlight: [7,12,20]; title: ; notranslate">
{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: &#x5B;
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;Federated&quot;: &quot;arn:aws:iam::123456789012:oidc-provider/gitlab.com&quot;
			},
			&quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
			&quot;Condition&quot;: {
				&quot;StringLike&quot;: {
					&quot;gitlab.com:sub&quot;: &quot;project_path:*/eks-gitlab:ref_type:branch:ref:main&quot;
				}
			}
		},
		{
			&quot;Sid&quot;: &quot;&quot;,
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;AWS&quot;: &quot;arn:aws:iam::123456789012:user/gitlab&quot;
			},
			&quot;Action&quot;: &quot;sts:AssumeRole&quot;
		}
	]
}
</pre>
<p>In line 7 and 20, change the <em>123456789012 </em>with your account number and in line 7 change <em>gitlab.com</em> with however you named the Identity Provider (IdP). In line 12 replace <em>eks-gitlab </em>with how you plan to name your GitLab project. In line 20 change the <em>gitlab </em>user to however you named your IAM user to access the EKS cluster. Click <strong>Next </strong>and when asked for a permission policy, choose <strong>AdministratorAccess</strong>. Best practice is to limit access to only what&#8217;s needed, but EKS has so many policies, it will take a lot to list them all.<br />
In case you created the gitlab IAM user, attach the AdministratorPolicy (fine tune later) and this inline policy. Change the ARN in line 7. Name this inline policy something like <em>polAssumeGitLabRole</em>. </p>
<pre class="brush: bash; highlight: [7]; title: ; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
            &quot;Resource&quot;: &quot;arn:aws:iam::123456789012:role/rolGitLab&quot;
        }
    ]
}
</pre>
<h1>GitLab</h1>
<p>Create a new blank project in Gitlab with the default settings. In my case the project name is called <strong>eks-gitlab</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1024x737.png" alt="" width="1024" height="737" class="aligncenter size-large wp-image-9615" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1024x737.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-300x216.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-768x553.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1170x843.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-585x421.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01.png 1497w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>Settings </strong>on the left, then <strong>CI/CD</strong> and then <strong>Variables</strong>. Add these two variables. </p>
<ul>
<li>AWS_PROFILE and a value of <strong>oidc</strong>. Yes, the value is <strong>oidc</strong>. Just type these 4 characters for the value.</li>
<li>ROLE_ARN and a value of the role ARN from the role you just created for <strong>rolGitLab</strong></li>
</ul>
<p>Create a <strong>.gitlab-ci.yml</strong> file with the following content. Change the domain for <em>gitlab.com</em> at line 7 if you use self-hosted GitLab.</p>
<pre class="brush: yaml; highlight: [7]; title: ; notranslate">
assume role:
  image: 
    name: amazon/aws-cli:latest
    entrypoint: &#x5B;&quot;&quot;]
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com
  before_script:
    - mkdir -p ~/.aws
    - echo &quot;${GITLAB_OIDC_TOKEN}&quot; &gt; /tmp/web_identity_token
    - echo -e &quot;&#x5B;profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token&quot; &gt; ~/.aws/config
  script:
      - aws sts get-caller-identity
      - aws s3 ls
</pre>
<p>By the time you commit changes, the pipeline will start and if everything is OK, you&#8217;ll see your S3 buckets in the logs of the job if you go to <strong>Build | Jobs</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-878x1024.png" alt="" width="878" height="1024" class="aligncenter size-large wp-image-9617" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-878x1024.png 878w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-257x300.png 257w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-768x895.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-585x682.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05.png 1012w" sizes="(max-width: 878px) 100vw, 878px" /></a><br />
One more thing related to Terraform state as we&#8217;ll keep the state in GitLab is that we have to create an <strong>Access Token</strong>. If you have a self-hosted or paid GitLab account, create an <em>Access Token</em> from the <em>Project | Settings | Access Tokens</em>.<br />
If you have a free GitLab account, you have to create a <strong>Personal Access Token</strong> and you do that if you click on your avatar in the upper left corner, then select <strong>Edit Profile</strong> and then click <strong>Access Tokens</strong> on the left. Click <strong>Add new token</strong>, name it <strong>GITLAB_PAT</strong> and select API only. Click <strong>Create personal access token</strong>. Once the token has been created click on the eye icon where it says <em>Your new personal access token</em> and copy &#038; paste somewhere safe.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-852x1024.png" alt="" width="852" height="1024" class="aligncenter size-large wp-image-9620" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-852x1024.png 852w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-250x300.png 250w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-768x923.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-585x703.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03.png 964w" sizes="(max-width: 852px) 100vw, 852px" /></a><br />
Go to the project settings and add these 2 variables.</p>
<ul>
<li>TF_USERNAME which is your username, click on the avatar, look at the @ under your name, your username is without the @ sign</li>
<li>TF_PASSWORD which is the value of your personal access token you just created</li>
</ul>
<h1>Local machine</h1>
<p>Now that we know the pipeline is working properly, let&#8217;s clone the project on our local machine.<br />
If you plan to manage the cluster from your local machine, now it&#8217;s time to configure the <strong>gitlab </strong>IAM user on your local machine using access keys.<br />
You need to have a valid <a href="https://docs.gitlab.com/ee/user/ssh.html" rel="noopener" target="_blank">key </a>in order to clone your private repo.<br />
Upload your .pub key to GitLab by clicking your avatar, then <strong>Edit Profile</strong>, click <strong>SSH Keys</strong> and then <strong>Add new key</strong>.<br />
Finally, check the connection with the following command. It doesn&#8217;t matter what your local Linux user is, just use git@gitlab.com as the username@hostname. Type as is. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ssh -T git@gitlab.com
Welcome to GitLab, @&#x5B;your_gitlab_username_here]
</pre>
<p>Once you clone the repo add the following files locally.<br />
<strong>FILE: .gitignore</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version 
# control as they are data points which are potentially sensitive and subject 
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc
</pre>
<p><strong>FILE: LICENSE</strong></p>
<pre class="brush: plain; collapse: true; light: false; title: ; toolbar: true; notranslate">
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to &lt;https://unlicense.org&gt;
</pre>
<p><strong>FILE: alb_ingress_iam_policy.json</strong></p>
<pre class="brush: xml; collapse: true; light: false; title: ; toolbar: true; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;iam:CreateServiceLinkedRole&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;iam:AWSServiceName&quot;: &quot;elasticloadbalancing.amazonaws.com&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:DescribeAccountAttributes&quot;,
                &quot;ec2:DescribeAddresses&quot;,
                &quot;ec2:DescribeAvailabilityZones&quot;,
                &quot;ec2:DescribeInternetGateways&quot;,
                &quot;ec2:DescribeVpcs&quot;,
                &quot;ec2:DescribeVpcPeeringConnections&quot;,
                &quot;ec2:DescribeSubnets&quot;,
                &quot;ec2:DescribeSecurityGroups&quot;,
                &quot;ec2:DescribeInstances&quot;,
                &quot;ec2:DescribeNetworkInterfaces&quot;,
                &quot;ec2:DescribeTags&quot;,
                &quot;ec2:GetCoipPoolUsage&quot;,
                &quot;ec2:DescribeCoipPools&quot;,
                &quot;elasticloadbalancing:DescribeLoadBalancers&quot;,
                &quot;elasticloadbalancing:DescribeLoadBalancerAttributes&quot;,
                &quot;elasticloadbalancing:DescribeListeners&quot;,
                &quot;elasticloadbalancing:DescribeListenerCertificates&quot;,
                &quot;elasticloadbalancing:DescribeSSLPolicies&quot;,
                &quot;elasticloadbalancing:DescribeRules&quot;,
                &quot;elasticloadbalancing:DescribeTargetGroups&quot;,
                &quot;elasticloadbalancing:DescribeTargetGroupAttributes&quot;,
                &quot;elasticloadbalancing:DescribeTargetHealth&quot;,
                &quot;elasticloadbalancing:DescribeTags&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;cognito-idp:DescribeUserPoolClient&quot;,
                &quot;acm:ListCertificates&quot;,
                &quot;acm:DescribeCertificate&quot;,
                &quot;iam:ListServerCertificates&quot;,
                &quot;iam:GetServerCertificate&quot;,
                &quot;waf-regional:GetWebACL&quot;,
                &quot;waf-regional:GetWebACLForResource&quot;,
                &quot;waf-regional:AssociateWebACL&quot;,
                &quot;waf-regional:DisassociateWebACL&quot;,
                &quot;wafv2:GetWebACL&quot;,
                &quot;wafv2:GetWebACLForResource&quot;,
                &quot;wafv2:AssociateWebACL&quot;,
                &quot;wafv2:DisassociateWebACL&quot;,
                &quot;shield:GetSubscriptionState&quot;,
                &quot;shield:DescribeProtection&quot;,
                &quot;shield:CreateProtection&quot;,
                &quot;shield:DeleteProtection&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:AuthorizeSecurityGroupIngress&quot;,
                &quot;ec2:RevokeSecurityGroupIngress&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateSecurityGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateTags&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:security-group/*&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;ec2:CreateAction&quot;: &quot;CreateSecurityGroup&quot;
                },
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateTags&quot;,
                &quot;ec2:DeleteTags&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:security-group/*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;true&quot;,
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:AuthorizeSecurityGroupIngress&quot;,
                &quot;ec2:RevokeSecurityGroupIngress&quot;,
                &quot;ec2:DeleteSecurityGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:CreateLoadBalancer&quot;,
                &quot;elasticloadbalancing:CreateTargetGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:CreateListener&quot;,
                &quot;elasticloadbalancing:DeleteListener&quot;,
                &quot;elasticloadbalancing:CreateRule&quot;,
                &quot;elasticloadbalancing:DeleteRule&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;,
                &quot;elasticloadbalancing:RemoveTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*&quot;
            ],
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;true&quot;,
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;,
                &quot;elasticloadbalancing:RemoveTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:ModifyLoadBalancerAttributes&quot;,
                &quot;elasticloadbalancing:SetIpAddressType&quot;,
                &quot;elasticloadbalancing:SetSecurityGroups&quot;,
                &quot;elasticloadbalancing:SetSubnets&quot;,
                &quot;elasticloadbalancing:DeleteLoadBalancer&quot;,
                &quot;elasticloadbalancing:ModifyTargetGroup&quot;,
                &quot;elasticloadbalancing:ModifyTargetGroupAttributes&quot;,
                &quot;elasticloadbalancing:DeleteTargetGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*&quot;
            ],
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;elasticloadbalancing:CreateAction&quot;: &#x5B;
                        &quot;CreateTargetGroup&quot;,
                        &quot;CreateLoadBalancer&quot;
                    ]
                },
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:RegisterTargets&quot;,
                &quot;elasticloadbalancing:DeregisterTargets&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:SetWebAcl&quot;,
                &quot;elasticloadbalancing:ModifyListener&quot;,
                &quot;elasticloadbalancing:AddListenerCertificates&quot;,
                &quot;elasticloadbalancing:RemoveListenerCertificates&quot;,
                &quot;elasticloadbalancing:ModifyRule&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
</pre>
<p><strong>FILE: backend.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
terraform {
  backend &quot;http&quot; {
  }
}
</pre>
<p><strong>FILE: data.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
data &quot;aws_availability_zones&quot; &quot;available&quot; {
}

data &quot;aws_eks_cluster&quot; &quot;cluster&quot; {
  name = module.eks.cluster_name
  depends_on = &#x5B;module.eks.cluster_name]
}

data &quot;aws_eks_cluster_auth&quot; &quot;cluster&quot; {
  name = module.eks.cluster_name
  depends_on = &#x5B;module.eks.cluster_name]
}
</pre>
<p><strong>FILE: main.tf NOTE:</strong> Make sure you add lines 40-41 and specify the <strong>rolGitlab</strong> role created earlier. If you don&#8217;t do this when you destroy the cluster, the KMS key for EKS will stay there and there is no way you can delete it unless you call AWS support.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Create a VPC
module &quot;vpc&quot; {
  source  = &quot;terraform-aws-modules/vpc/aws&quot;
  version = &quot;5.0.0&quot;

  name = var.vpcName

  cidr = var.vpcCIDR
  azs  = slice(data.aws_availability_zones.available.names, 0, 3)

  private_subnets = var.privateSubnets
  public_subnets  = var.publicSubnets

  enable_nat_gateway   = var.enable_nat_gateway
  single_nat_gateway   = var.single_nat_gateway
  enable_dns_hostnames = var.enable_dns_hostnames

  public_subnet_tags = {
    &quot;kubernetes.io/cluster/${var.cluster_name}&quot; = &quot;shared&quot;
    &quot;kubernetes.io/role/elb&quot;                    = 1
  }

  private_subnet_tags = {
    &quot;kubernetes.io/cluster/${var.cluster_name}&quot; = &quot;shared&quot;
    &quot;kubernetes.io/role/internal-elb&quot;           = 1
  }
}

# Create an EKS cluster
module &quot;eks&quot; {
  source  = &quot;terraform-aws-modules/eks/aws&quot;
  version = &quot;19.15.3&quot;

  cluster_name    = var.cluster_name
  cluster_version = var.cluster_version

  vpc_id                         = module.vpc.vpc_id
  subnet_ids                     = module.vpc.private_subnets
  cluster_endpoint_public_access = var.cluster_endpoint_public_access
  kms_key_administrators         = &#x5B;&quot;arn:aws:iam::123456789012:role/rolGitLab&quot;]
  kms_key_enable_default_policy  = true

  eks_managed_node_group_defaults = {
    ami_type = var.ami_type
  }

  eks_managed_node_groups = {
    one = {
      name = var.nodeGroup

      instance_types = var.instanceTypes

      min_size     = var.minSize
      max_size     = var.maxSize
      desired_size = var.desiredSize
    }
  }
}

# Create an IAM policy for the ALB ingress
resource &quot;aws_iam_policy&quot; &quot;worker_policy&quot; {
  name        = &quot;worker-policy&quot;
  description = &quot;Worker policy for the ALB Ingress&quot;

  policy = file(var.iamALBIngressPolicyFileName)
}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;additional&quot; {
  for_each = module.eks.eks_managed_node_groups

  policy_arn = aws_iam_policy.worker_policy.arn
  role       = each.value.iam_role_name
}

# Crete the Ingress using Helm chart from AWS
resource &quot;helm_release&quot; &quot;ingress&quot; {
  name       = &quot;ingress&quot;
  chart      = &quot;aws-load-balancer-controller&quot;
  repository = &quot;https://aws.github.io/eks-charts&quot;
  version    = &quot;1.6.1&quot;

  set {
    name  = &quot;autoDiscoverAwsRegion&quot;
    value = &quot;true&quot;
  }

  set {
    name  = &quot;autoDiscoverAwsVpcID&quot;
    value = &quot;true&quot;
  }

  set {
    name  = &quot;clusterName&quot;
    value = module.eks.cluster_name
  }
}
</pre>
<p><strong>FILE: outputs.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
output &quot;cluster_endpoint&quot; {
  description = &quot;Endpoint for EKS control plane&quot;
  value       = module.eks.cluster_endpoint
}

output &quot;cluster_security_group_id&quot; {
  description = &quot;Security group ids attached to the cluster control plane&quot;
  value       = module.eks.cluster_security_group_id
}

output &quot;region&quot; {
  description = &quot;AWS region&quot;
  value       = var.region
}

output &quot;cluster_name&quot; {
  description = &quot;Kubernetes Cluster Name&quot;
  value       = module.eks.cluster_name
}
</pre>
<p><strong>FILE: providers.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
provider &quot;aws&quot; {
  region = var.region

  shared_config_files      = &#x5B;&quot;~/.aws/config&quot;]  
  profile                  = &quot;oidc&quot;
}

provider &quot;helm&quot; {
  kubernetes {
    host                   = data.aws_eks_cluster.cluster.endpoint
    token                  = data.aws_eks_cluster_auth.cluster.token
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
  }
}
</pre>
<p><strong>FILE: variables.tf</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
variable &quot;region&quot; {
  type    = string
  default = &quot;us-east-2&quot;
}

variable &quot;vpcName&quot; {
  type    = string
  default = &quot;vpcFromGitLab&quot;
}

variable &quot;vpcCIDR&quot; {
  type    = string
  default = &quot;10.0.0.0/16&quot;
}

variable &quot;privateSubnets&quot; {
  type    = list(string)
  default = &#x5B;&quot;10.0.1.0/24&quot;, &quot;10.0.2.0/24&quot;, &quot;10.0.3.0/24&quot;]
}

variable &quot;publicSubnets&quot; {
  type    = list(string)
  default = &#x5B;&quot;10.0.4.0/24&quot;, &quot;10.0.5.0/24&quot;, &quot;10.0.6.0/24&quot;]
}

variable &quot;enable_nat_gateway&quot; {
  type    = bool
  default = true
}

variable &quot;single_nat_gateway&quot; {
  type    = bool
  default = false
}

variable &quot;enable_dns_hostnames&quot; {
  type    = bool
  default = true
}

variable &quot;cluster_name&quot; {
  type    = string
  default = &quot;eksFromGitLab&quot;
}

variable &quot;cluster_version&quot; {
  type    = string
  default = &quot;1.27&quot;
}

variable &quot;cluster_endpoint_public_access&quot; {
  type    = bool
  default = true
}

variable &quot;ami_type&quot; {
  type    = string
  default = &quot;AL2_x86_64&quot;
}

variable &quot;nodeGroup&quot; {
  type    = string
  default = &quot;ngFromGitLab&quot;
}

variable &quot;instanceTypes&quot; {
  type    = list(string)
  default = &#x5B;&quot;t3.small&quot;]
}

variable &quot;minSize&quot; {
  type    = number
  default = 1
}

variable &quot;maxSize&quot; {
  type    = number
  default = 3
}

variable &quot;desiredSize&quot; {
  type    = number
  default = 3
}


variable &quot;iamALBIngressPolicyFileName&quot; {
  type    = string
  default = &quot;alb_ingress_iam_policy.json&quot;
}
</pre>
<p>&#8230;and finally your .gitlab-ci.yml file.<br />
<strong>FILE: .gitlab-ci.yml. NOTE: </strong>Remove line 38 if you don&#8217;t use it, don&#8217;t keep it with a comment or you&#8217;ll get an error in the pipeline.</p>
<pre class="brush: bash; highlight: [38]; title: ; notranslate">
stages:          
  - validate
  - plan
  - apply
  - destroy

image:
  name: hashicorp/terraform:1.6
  entrypoint:
    - &quot;/usr/bin/env&quot;
    - &quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;

variables:
  TF_DIR: ${CI_PROJECT_DIR}
  TF_STATE_NAME: &quot;eks-tf-state&quot;
  TF_ADDRESS: &quot;https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}&quot;

default:
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com

  before_script:
    - terraform --version
    - cd ${TF_DIR}
    - terraform init
      -reconfigure 
      -backend-config=&quot;address=${TF_ADDRESS}&quot; 
      -backend-config=&quot;lock_address=${TF_ADDRESS}/lock&quot; 
      -backend-config=&quot;unlock_address=${TF_ADDRESS}/lock&quot; 
      -backend-config=&quot;username=${TF_USERNAME}&quot; 
      -backend-config=&quot;password=${TF_PASSWORD}&quot; 
      -backend-config=&quot;lock_method=POST&quot; 
      -backend-config=&quot;unlock_method=DELETE&quot; 
      -backend-config=&quot;retry_wait_min=5&quot;
    - mkdir -p ~/.aws
    - echo &quot;${GITLAB_OIDC_TOKEN}&quot; &gt; /tmp/web_identity_token
    - # echo &quot;${GITLAB_OIDC_TOKEN}&quot; | base64 - If you want to see the token, reveal it this way and then use base64 --decode to get the real one
    - echo -e &quot;&#x5B;profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token&quot; &gt; ~/.aws/config

job-validate:
  stage: validate
  script:
    - terraform validate

job-plan:
  stage: plan
  script:
    - terraform plan -out &quot;tf_plan&quot;
  dependencies:
    - job-validate
  artifacts:
    paths:
      - tf_plan

job-apply:
  stage: apply
  script:
    - terraform apply &quot;tf_plan&quot;
  dependencies:
    - job-plan
  allow_failure: true

job-destroy:
  stage: destroy
  script:
    - terraform destroy  -auto-approve
  dependencies:
    #- job-plan
    #- job-apply
  when: manual
</pre>
<p>Push the code back to Gitlab (<em>git add, git commit, git push</em>) and once everything completes successfully, you&#8217;ll have a working EKS cluster after 10-15 mins.<br />
If you choose to manage the EKS from GitLab, you can skip to the next section. If you decide to manage the cluster outside GitLab and you already have another IAM user added to the role <strong>rolGitLab </strong>as described above, then do the following. Replace account # in line 1.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws eks update-kubeconfig --region us-east-2 --name eksFromGitLab --role-arn arn:aws:iam::123456789012:role/rolGitLab
aws eks list-clusters
eksctl get cluster --name=eksFromGitLab --region us-east-2
kubectl get svc
</pre>
<h1>Manage EKS from GitLab</h1>
<p>Generally, you would want this to be in another project. Our first project was to build the cluster, but our second project builds on top of that and manages the cluster. In addition, the second project also uses the CI/CD pipeline file <strong>.gitlab-ci.yml</strong> so in order to avoid a single complex project &#8211; just create a new project. I&#8217;ll name my new project &#8211; <strong>project1</strong>. Clone the project on your local machine using <em>git clone</em>.<br />
Create an empty config file for the agent on your local machine. It has to be under this folder structure.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p .gitlab/agents/eks-agent &amp;&amp; touch .gitlab/agents/eks-agent/config.yaml
</pre>
<p>In my case the name of the agent is <strong>eks-agent</strong>. Use whatever name you want but follow DNS naming <a href="https://docs.gitlab.com/ee/user/clusters/agent/install/index.html" rel="noopener" target="_blank">convention</a>.<br />
Push the local changes to GitLab.<br />
On the left side in GitLab, go to <strong>Operate | Kubernetes clusters</strong> and then click <strong>Connect a cluster</strong> from the upper right corner.<br />
Select the agent from the drop down and click <strong>Register</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1024x304.png" alt="" width="1024" height="304" class="aligncenter size-large wp-image-9767" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1024x304.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-300x89.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-768x228.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1170x347.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-585x174.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02.png 1522w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
You&#8217;ll get an access token and a bunch of helm commands to install the agent on the cluster. Copy and paste and run those commands.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add gitlab https://charts.gitlab.io
helm repo update
helm upgrade --install eks-agent gitlab/gitlab-agent \
    --namespace gitlab-agent-eks-agent \
    --create-namespace \
    --set image.tag=v16.9.0-rc2 \
    --set config.token=glagent-2r_uW63e1w_mkNzR_keU2hmaNhhcKauSpujg8OdLQG74wrF73g \
    --set config.kasAddress=wss://kas.gitlab.com
</pre>
<p>Refresh the GitLab screen after the helm chart has been installed and you&#8217;ll see your cluster connected.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1024x174.png" alt="" width="1024" height="174" class="aligncenter size-large wp-image-9769" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1024x174.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-300x51.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-768x131.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1536x261.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-2048x348.png 2048w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1920x326.png 1920w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1170x199.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-585x99.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now you need to <a href="https://docs.gitlab.com/ee/user/clusters/agent/ci_cd_workflow.html#authorize-the-agent" rel="noopener" target="_blank">authorize</a> the agent to access our project.<br />
In the blank <strong>config.yaml</strong> file on the local machine under <em>.gitlab/agents/eks-agent/</em> directory, add these lines. Change the path the project in line 3 (look at the URL, that&#8217;s your project name after gitlab.com) and change the manifest file if you want in line 6. You can also change the namespace in line 5 as well.</p>
<pre class="brush: yaml; highlight: [3,6]; title: ; notranslate">
gitops:
  manifest_projects:
  - id: andreevkliment/project1
    default_namespace: default
    paths:
      - glob: &#039;manifest.yaml&#039;
</pre>
<p>Then create the manifest file under the root of the project. Save it as <em>manifest.yaml</em> or however you referenced it in line 6 in the previous file.</p>
<pre class="brush: yaml; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
</pre>
<p>If you push this code back to GitLab, the agent will execute it and you&#8217;ll (with <em>kubect get pods</em>) see the nginx pods running on your cluster.<br />
Another way of managing the EKS cluster is using <strong>.gitlab-ci.yml</strong> file.<br />
Create <strong>.gitlab-ci.yml</strong> with these lines. Replace your username, project and the agent name in line 7.</p>
<pre class="brush: yaml; highlight: [7]; title: ; notranslate">
deploy:
  image:
    name: bitnami/kubectl:latest
    entrypoint: &#x5B;&#039;&#039;]
  script:
    - kubectl config get-contexts
    - kubectl config use-context andreevkliment/project1:eks-agent
    - kubectl get pods
</pre>
<p>If you push this back to GitLab, the pipeline will start and you&#8217;ll see the pods from the cluster in the logs. This example doesn&#8217;t execute <strong>manifest.yaml</strong>. You can have both ways of interacting with the cluster or you can use them separately.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Install MetalLB on Kubernetes cluster running on VMWare VMs or bare metal server and nginx ingress</title>
		<link>https://blog.andreev.it/2023/10/install-metallb-on-kubernetes-cluster-running-on-vmware-vms-or-bare-metal-server/</link>
					<comments>https://blog.andreev.it/2023/10/install-metallb-on-kubernetes-cluster-running-on-vmware-vms-or-bare-metal-server/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sat, 21 Oct 2023 23:05:18 +0000</pubDate>
				<category><![CDATA[DevOps]]></category>
		<category><![CDATA[ESX/ESXi]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[bare metal]]></category>
		<category><![CDATA[certificate]]></category>
		<category><![CDATA[ESXi]]></category>
		<category><![CDATA[ingress]]></category>
		<category><![CDATA[load balancer]]></category>
		<category><![CDATA[MetalLB]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[ssl]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[VMware]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9551</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to install MetalLB which is a load balancer&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to install <a href="https://metallb.universe.tf/" rel="noopener" target="_blank">MetalLB </a>which is a load balancer for bare metal/VM Kubernetes servers. If you have your own home lab to play with Kubernetes and you are not using AKS, EKS or GKE, then this post is for you.<br />
The prerequisite is to have a running Kubernetes cluster. In my earlier <a href="https://blog.andreev.it/2023/10/install-kubernetes-with-cri-o-and-cilium-on-rocky-linux-9/" rel="noopener" target="_blank">post</a>, I&#8217;ve described how to install Kubernetes with CRI-O and Cilium as a CNI, so you can follow that post or feel free to have your own install. Mind that if you use other CNIs such as Calico or Weave, there are some things you have to check first and see if it applies to you. Look at this <a href="https://metallb.universe.tf/configuration/" rel="noopener" target="_blank">link</a>.<br />
We&#8217;ll install MetalLB now. You probably have your k8s master and the nodes ready.</p>
<h1>Install MetalLB</h1>
<p>Do this first on the master.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl edit configmap -n kube-system kube-proxy
</pre>
<p>&#8230;and change <strong>strictARP </strong>to true.</p>
<pre class="brush: bash; title: ; notranslate">
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: &quot;ipvs&quot;
ipvs:
  strictARP: true
</pre>
<p>Deploy MetalLB.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.11/config/manifests/metallb-native.yaml
</pre>
<p>You&#8217;ll see the pods running in metallb-system namespace.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-7d56b4f464-gkfrq   1/1     Running   0          55s
speaker-l5954                 1/1     Running   0          55s
speaker-xrswp                 1/1     Running   0          55s
speaker-z8vdt                 1/1     Running   0          55s
</pre>
<p>Let&#8217;s configure our load balancer to give IPs in this 192.168.1.50-192.168.1.60 range. Change to your needs accordingly in line 8.<br />
Save it as a <strong>lb-config.yaml</strong> file.</p>
<pre class="brush: bash; highlight: [8]; title: ; notranslate">
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.50-192.168.1.60
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
</pre>
<p>Deploy the config.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f lb-config.yaml
</pre>
<p>Now, we can deploy a simple app and a load balancer. Save the file as <strong>demo.yaml</strong>.<br />
This is an app that I made in Node.js that listens on port 3000 to display the IP of the pod that the load balancer hits.<br />
You can use it for any type of a load balancer, not just MetalLB. It&#8217;s a super simple app that prints the IP of the pod where it&#8217;s running.</p>
<pre class="brush: bash; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 6
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      containers:
      - name: demo
        image: klimenta/serverip
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
spec:
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
  type: LoadBalancer
  selector:
    run: demo
</pre>
<p>Deploy the app.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>Check the load balancer service.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1      &lt;none&gt;         443/TCP        33m
loadbalancer   LoadBalancer   10.106.36.58   192.168.1.50   80:30770/TCP   4m33s
</pre>
<p>&#8230;and if you go to that IP (192.168.1.50) in your browser, you&#8217;ll see the IP of the pods that the load balancer hits.</p>
<h1>Install nginx ingress reverse proxy</h1>
<p>The problem with the scenario above is that for every service you need to expose, you need a different IP from the IP pool that we&#8217;ve assigned to MetalLB (.50-.60).<br />
A better solution is to install an nginx ingress that will act as a reverse proxy, but for this you&#8217;ll need a working DNS server as the services will share the same IP, but different FQDN.<br />
So, in practice it looks like this.<br />
Install nginx ingress first.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
kubectl get pods -n ingress-nginx
kubectl get service ingress-nginx-controller -n=ingress-nginx
</pre>
<p>Don&#8217;t worry if the two nginx ports are not running and it says &#8211; &#8220;completed&#8221;.<br />
You&#8217;ll see the external IP that&#8217;s assigned to nginx from MetalLB.<br />
For some reason, nginx ingress won&#8217;t work without this line. Many people have this problem in a bare-metal environment and this is the workaround.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
</pre>
<p>Now, let&#8217;s create an nginx web server. This has nothing to do with nginx ingress, it&#8217;s completely separate.<br />
I am exposing the nginx deploymenty and creating a rule that will hit the deployment on port 80 with nginx.homelab.local as FQDN.<br />
NOTE: If your browser can&#8217;t resolve this URL, it won&#8217;t work. So, make sure you have a DNS or /etc/hosts file or some type of name resolution.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create deployment nginx --image=nginx --port=80
kubectl expose deployment nginx
kubectl create ingress nginx --class=nginx --rule nginx.homelab.local/=nginx:80
</pre>
<p>Then, let&#8217;s install Apache server that runs on the same IP and same port 80.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create deployment httpd --image=httpd --port=80
kubectl expose deployment httpd
kubectl create ingress httpd --class=nginx --rule httpd.homelab.local/=httpd:80
</pre>
<p>If everything is OK, you&#8217;ll be able to access both web servers using their URLs.<br />
If you want to put Grafana and/or KubeCost behind nginx ingress, read these short posts <a href="https://blog.andreev.it/2023/04/general-tips-tricks-and-one-liners-part-iv/#Install_Kube_Prometheus_stack_on_NFS" rel="noopener" target="_blank">here </a>and <a href="https://blog.andreev.it/2023/04/general-tips-tricks-and-one-liners-part-iv/#Install_KubeCost_on_NFS" rel="noopener" target="_blank">here</a>.<br />
If you want to use yaml for deployment, here is an example of that. It creates 6 replicas of a pod that prints the IP where the pod is running. The service runs on port 3000. Then it creates a ClusterIP on the same port and puts the ClusterIP behind the ingress. Change the hostname at line 39.</p>
<pre class="brush: xml; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 6
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      containers:
      - name: demo
        image: klimenta/getmyip
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: demo
spec:
  type: ClusterIP
  ports:
  - port: 3000
    targetPort: 3000
  selector:
    run: demo
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-demo
spec:
  rules:
  - host: demo.homelab.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo
            port:
              number: 3000
  ingressClassName: nginx
</pre>
<h1>SSL certificates</h1>
<p>The examples above allow you to run multiple websites behind a reverse proxy on port 80, but in reality you want everything on port 443 and with SSL certificates. I&#8217;ll describe two scenarios: with a valid cert and self-signed certificate.Fortunately, nginx ingress listens on port 443 and does automatic redirect from http->https so you don&#8217;t have to worry about any changes.</p>
<h2>Valid SSL certificate</h2>
<p>You will need a valid SSL certificate for this. And you will need the private key, the certificate, the CA root certificate and the intermediate certificate. You can get these from the vendor where you purchased the certificate. In my case, I&#8217;ll have the private key saved as domain.key, the certificate as domain.pem, the root CA certificate as domain.ca and the intermediate certificate as domain.inter.<br />
<strong>IMPORTANT</strong>: nginx expects the certificate chain in exact order: PEM -> INTERMEDIATE -> ROOT CA. If you have just the private key and the certificate (without inter and root CA), it might work, but you might get some errors when using curl, such as that the certificate authority cannot be recognized.<br />
So, create a file called domain.full.pem or whatever that has all 3 certificates in that order, e.g. <em>cat domain.pem domain.inter domain.ca > domain.full.pem.</em><br />
If you received a bundle file from the certificate vendor, create a chain from two files: domain.pem plus the bundle file. The bundle file contains the intermediate and the root certificate.<br />
Now that you have these files, you need to create a TLS secret in Kubernetes where you&#8217;ll store your certificate. You can do that with kubectl or with a YAML file.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create secret tls demo-tls --key domain.key --cert domain.full.pem
</pre>
<p>Keep the secret, ingress and the deployment in the same namespace.<br />
If you use YAML, which most likely you&#8217;ll do if you use automation, you&#8217;ll have to encode the certificates.<br />
This is the template YAML file. Name it as <em>tls.yaml</em>.</p>
<pre class="brush: bash; title: ; notranslate">
apiVersion: v1
kind: Secret
metadata:
  name: demo-tls
type: kubernetes.io/tls
data:
  tls.crt: |
    &lt;insert_base64_string&gt;
  tls.key: |
    &lt;insert_base64_string&gt;
</pre>
<p>Don&#8217;t change data keys tls.crt and tls.key. You have to use them, not something like key.crt or something.crt. Use exactly tls.crt and tls.key.<br />
Encode the certificate with base64.</p>
<pre class="brush: bash; title: ; notranslate">
base64 -w0 domain.full.pem
</pre>
<p>Encode the key with base64.</p>
<pre class="brush: bash; title: ; notranslate">
base64 -w0 domain.key
</pre>
<p>You&#8217;ll get two long strings that start with LS0&#8230;Copy &#038; paste these strings into YAML and make sure they are properly indented.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/10/P166-01.png" data-rel="penci-gallery-image-content" ><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/10/P166-01.png" alt="" width="811" height="520" class="aligncenter size-full wp-image-9697" srcset="https://blog.andreev.it/wp-content/uploads/2023/10/P166-01.png 811w, https://blog.andreev.it/wp-content/uploads/2023/10/P166-01-300x192.png 300w, https://blog.andreev.it/wp-content/uploads/2023/10/P166-01-768x492.png 768w, https://blog.andreev.it/wp-content/uploads/2023/10/P166-01-585x375.png 585w" sizes="(max-width: 811px) 100vw, 811px" /></a><br />
Now, you can create the secret.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f tls.yaml
</pre>
<p>All yopu have to do now is to reference the TLS secret that we created. Compare the highlighted lines in this YAML with the previous one.</p>
<pre class="brush: bash; highlight: [6,7,8,9,10,11]; title: ; notranslate">
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-demo
spec:
  tls:
  - hosts:
    - demo.iandreev.com
    secretName: demo-tls
  rules:
  - host: demo.iandreev.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo
            port:
              number: 3000
  ingressClassName: nginx
</pre>
<p>Change the hostname in line 11 and 14 to match your cert and you can deploy the app. </p>
<h2>Self-signed certificates</h2>
<p>First, we have to create a Certificate Authority (CA). I&#8217;ll use <em>homelab.local</em> as my domain.</p>
<pre class="brush: bash; title: ; notranslate">
openssl req -x509 -nodes -sha512 -days 365 -newkey rsa:4096 \
 -keyout rootCA.key -out rootCA.crt -subj &quot;/C=US/CN=HomeLab Certificate Authority&quot;
</pre>
<p>You&#8217;ll get two files, rootCA.key (the private key) and rootCA.crt.<br />
Create a private key for the wildcard homelab.local domain and a certificate signing request file.<br />
You&#8217;ll get two files, star.homelab.local.key (the private key for the domain) and star.homelab.local.csr (the certificate signing request).</p>
<pre class="brush: bash; title: ; notranslate">
openssl req -new -nodes -sha512 -days 365 -newkey rsa:4096 \
 -keyout star.homelab.local.key -out star.homelab.local.csr -subj &quot;/C=US/ST=NJ/L=Trenton/O=HomeLab Org/CN=*.homelab.local&quot;
</pre>
<p>Create this file where you&#8217;ll specify your domain name and eventually your IP, but the IP is not necessary.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
&#x5B;alt_names]
DNS.1 = *.homelab.local
IP.1 = 192.168.1.50
EOF
</pre>
<p>Sign the certificate signing request and create the certificate file. You&#8217;ll get one certificate file star.homelab.local.crt.</p>
<pre class="brush: bash; title: ; notranslate">
openssl x509 -req -sha512 -days 365 -in star.homelab.local.csr -CA rootCA.crt -CAkey rootCA.key \
 -CAcreateserial -out star.homelab.local.crt  -extfile v3.ext
</pre>
<p>Combine the certificate and the CA certificate in a single file.</p>
<pre class="brush: bash; title: ; notranslate">
cat star.homelab.local.crt rootCA.crt &gt; star.homelab.local.chain.crt
</pre>
<p>Create this tls.yaml template file.</p>
<pre class="brush: bash; title: ; notranslate">
apiVersion: v1
kind: Secret
metadata:
  name: demo-tls
type: kubernetes.io/tls
data:
  tls.crt: |
    &lt;insert_base64_string&gt;
  tls.key: |
    &lt;insert_base64_string&gt;
</pre>
<p>Encode both the domain certificate chain file and the domain private key file.</p>
<pre class="brush: bash; title: ; notranslate">
base64 -w0 star.homelab.local.chain.crt
base64 -w0 star.homelab.local.key
</pre>
<p>You&#8217;ll get two very long single lines. Add these two lines to the tls.yaml template file but make sure they are properly indented (see the screenshot in the previous section).<br />
Now, you can create the secret.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f tls.yaml
</pre>
<p>You can also use kubectl to create the secret, see the previous section.<br />
Save this file as <em>demo-local.yaml</em> and deploy it using <strong>kubectl apply -f demo-local.yaml</strong>.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 6
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      containers:
      - name: demo
        image: klimenta/getmyip
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: demo
spec:
  type: ClusterIP
  ports:
  - port: 3000
    targetPort: 3000
  selector:
    run: demo
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-demo
spec:
  tls:
  - hosts:
    - demo.homelab.local
    secretName: demo-tls
  rules:
  - host: demo.homelab.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo
            port:
              number: 3000
  ingressClassName: nginx
</pre>
<p>You&#8217;ll be able to go to your browser and check demo.homelab.local and see the page after the SSL warning.<br />
But if you double-click the rootCA.crt file on your local machine, you can import the certificate in the Trusted Root Certificates store and you won&#8217;t get that warning anymore.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/10/install-metallb-on-kubernetes-cluster-running-on-vmware-vms-or-bare-metal-server/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
