<?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>AWS &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/category/cloud/aws/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Mon, 28 Oct 2024 14:36:49 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<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"><img fetchpriority="high" 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"><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"><img 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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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>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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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"><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>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"><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"><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"><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"><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"><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"><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"><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>AWS: Access DynamoDB and Secrets Manager from Node.js on EC2 and EKS</title>
		<link>https://blog.andreev.it/2023/09/aws-access-dynamodb-and-secrets-manager-from-node-js-on-ec2-and-eks/</link>
					<comments>https://blog.andreev.it/2023/09/aws-access-dynamodb-and-secrets-manager-from-node-js-on-ec2-and-eks/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 24 Sep 2023 15:12:07 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9422</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to access DynamoDB table and Secrets Manager from&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to access DynamoDB table and Secrets Manager from an EC2 instance running a small Node.js/Express app. The app will access the DynamoDB and print an entry from the table and also access an API Gateway that will call a lambda function that returns the current date and time. The API Gateway will be protected with a header authentication and we&#8217;ll store the header auth token in Secrets Manager, which means our app will have to access the Secret Manager as well. Finally, we&#8217;ll containerize that app as a Docker image and run it as a Deployment behind a load balancer on EKS.<br />
There are a lot of steps, so make sure to use the same variable names and pay attention to the outputs.<br />
NOTE: There are multiple ways of accessing Secrets Manager. The one described here is with roles and AWS SDK. There is another way of accessing Secrets Manager from EKS using Kubernetes CSI driver, so make sure you read <a href="https://blog.andreev.it/2024/01/aws-get-secrets-from-secrets-manager-using-csi-driver-and-ascp/" rel="noopener" target="_blank">this</a> post too if you use EKS and Secrets Manager only.</p>
<h1>Lambda and API Gateway</h1>
<p>Let&#8217;s create a Lambda function and an API Gateway so when the API Gateway is called with a parameter, it will write the parameter, current date/time and on top of that we&#8217;ll protect the API Gateway so it can be called only if we pass a password/token.<br />
Define the default region.</p>
<pre class="brush: bash; title: ; notranslate">
AWS_DEFAULT_REGION=&quot;us-east-2&quot;
</pre>
<p>Get the account number.</p>
<pre class="brush: bash; title: ; notranslate">
ACCT_NO=$(aws sts get-caller-identity --query &quot;Account&quot; --output text)
echo $ACCT_NO
</pre>
<p>This is the Hello World Node.js Lambda function source.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;&#039;EOF&#039; &gt; helloworld.js
&#039;use strict&#039;;
 
exports.handler = async (event) =&gt; {
    let name = &quot;&quot;;
    let responseCode = 200;
    const date = new Date();
    
    if (event.queryStringParameters &amp;&amp; event.queryStringParameters.name) {
        name = event.queryStringParameters.name;
    }
    
    if (event.body) {
        let body = JSON.parse(event.body)
    }
 
    let greeting = `Hello ${name}, it&#039;s ${date}`;
    

    let responseBody = {
        message: greeting,
        input: event
    };
    
    let response = {
        statusCode: responseCode,
        headers: {
            &quot;x-custom-header&quot; : &quot;my custom header value&quot;
        },
        body: JSON.stringify(responseBody)
    };
    console.log(&quot;response: &quot; + JSON.stringify(response))
    return response;
};
EOF
</pre>
<p>Zip the function so we can deploy it.</p>
<pre class="brush: bash; title: ; notranslate">
zip helloworld.zip helloworld.js
</pre>
<p>We need a Lambda role that allows execution.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; lambda-role.json
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: &#x5B;
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
      &quot;Service&quot;: &#x5B;
        &quot;lambda.amazonaws.com&quot;
      ]
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
EOF
</pre>
<p>Create the role and attach it to a policy.</p>
<pre class="brush: bash; title: ; notranslate">
ROLE_ARN=$(aws iam create-role --role-name rolLambdaExecutionRole \
  --assume-role-policy-document file://lambda-role.json --output text --query &#039;Role.Arn&#039;)
echo $ROLE_ARN
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \
  --role-name rolLambdaExecutionRole
</pre>
<p>Create the Lambda function.</p>
<pre class="brush: bash; title: ; notranslate">
LAMBDA_HELLOWORLD_ARN=$(aws lambda create-function --function-name HelloWorld \
  --zip-file fileb://helloworld.zip --runtime nodejs18.x --role $ROLE_ARN \
  --handler helloworld.handler --output text --query &#039;FunctionArn&#039;)
echo $LAMBDA_HELLOWORLD_ARN
</pre>
<p>We also need another Lambda function that will be used as an authorizer. This will allow us to protect the API Gateway with authentication. The authentication parameters are in line 27 here on the screen.(<em>headerauth1, password</em>).</p>
<pre class="brush: bash; highlight: [27]; title: ; notranslate">
cat &lt;&lt; EOF &gt; authorize.js
exports.handler = function(event, context, callback) {
    console.log(&#039;Received event:&#039;, JSON.stringify(event, null, 2));

    // Retrieve request parameters from the Lambda function input:
    var headers = event.headers;
        
    // Parse the input for the parameter values
    var tmp = event.methodArn.split(&#039;:&#039;);
    var apiGatewayArnTmp = tmp&#x5B;5].split(&#039;/&#039;);
    var awsAccountId = tmp&#x5B;4];
    var region = tmp&#x5B;3];
    var restApiId = apiGatewayArnTmp&#x5B;0];
    var stage = apiGatewayArnTmp&#x5B;1];
    var method = apiGatewayArnTmp&#x5B;2];
    var resource = &#039;/&#039;; // root resource
    if (apiGatewayArnTmp&#x5B;3]) {
        resource += apiGatewayArnTmp&#x5B;3];
    }
        
    // Perform authorization to return the Allow policy for correct parameters and 
    // the &#039;Unauthorized&#039; error, otherwise.
    var authResponse = {};
    var condition = {};
    condition.IpAddress = {};
     
    if (headers.headerauth1 === &quot;password&quot;) 
    {
        callback(null, generateAllow(&#039;me&#039;, event.methodArn));
    }  else {
        callback(&quot;Unauthorized&quot;);
    }
}
     
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
    // Required output:
    var authResponse = {};
    authResponse.principalId = principalId;
    if (effect &amp;&amp; resource) {
        var policyDocument = {};
        policyDocument.Version = &#039;2012-10-17&#039;; // default version
        policyDocument.Statement = &#x5B;];
        var statementOne = {};
        statementOne.Action = &#039;execute-api:Invoke&#039;; // default action
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement&#x5B;0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    // Optional output with custom properties of the String, Number or Boolean type.
    authResponse.context = {
        &quot;stringKey&quot;: &quot;stringval&quot;,
        &quot;numberKey&quot;: 123,
        &quot;booleanKey&quot;: true
    };
    return authResponse;
}
     
var generateAllow = function(principalId, resource) {
    return generatePolicy(principalId, &#039;Allow&#039;, resource);
}
     
var generateDeny = function(principalId, resource) {
    return generatePolicy(principalId, &#039;Deny&#039;, resource);
}
EOF
</pre>
<p>Zip it so we can deploy it.</p>
<pre class="brush: bash; title: ; notranslate">
zip authorize.zip authorize.js
</pre>
<p>Create the Lambda authorizer function and use the same role as with the Hello World function.</p>
<pre class="brush: bash; title: ; notranslate">
LAMBDA_AUTH_ARN=$(aws lambda create-function --function-name Authorize1 --zip-file fileb://authorize.zip --runtime nodejs18.x \
  --role $ROLE_ARN --handler authorize.handler --output text --query &#039;FunctionArn&#039;)
echo $LAMBDA_AUTH_ARN
</pre>
<p>Create an API Gateway.</p>
<pre class="brush: bash; title: ; notranslate">
APIGW_ID=$(aws apigateway create-rest-api --name &#039;gwapihelloworld&#039; \
  --endpoint-configuration &#039;{&quot;types&quot;: &#x5B;&quot;REGIONAL&quot;]}&#039; --output text --query &#039;id&#039;)
echo $APIGW_ID
</pre>
<p>Create a top level resource.</p>
<pre class="brush: bash; title: ; notranslate">
ROOT_ID=$(aws apigateway get-resources --rest-api-id $APIGW_ID --output text --query &#039;items&#x5B;0].id&#039;)
echo $ROOT_ID
RES_ID=$(aws apigateway create-resource --rest-api-id $APIGW_ID \
      --parent-id $ROOT_ID \
      --path-part helloworld --output text --query &#039;id&#039;)
echo $RES_ID
</pre>
<p>Create an ANY method without authorization.</p>
<pre class="brush: bash; title: ; notranslate">
aws apigateway put-method --rest-api-id $APIGW_ID \
       --resource-id $RES_ID \
       --http-method ANY \
       --authorization-type &quot;NONE&quot;
</pre>
<p>Integrate the Lambda function with the API Gateway.</p>
<pre class="brush: bash; title: ; notranslate">
aws apigateway put-integration \
        --rest-api-id $APIGW_ID \
        --resource-id $RES_ID \
        --http-method ANY \
        --type AWS_PROXY \
        --integration-http-method POST \
        --uri arn:aws:apigateway:$AWS_DEFAULT_REGION:lambda:path/2015-03-31/functions/$LAMBDA_HELLOWORLD_ARN/invocations 
</pre>
<p>Allow Lambda function to be triggered by the API Gateway.</p>
<pre class="brush: bash; title: ; notranslate">
aws lambda add-permission --function-name $LAMBDA_HELLOWORLD_ARN \
        --action lambda:InvokeFunction --statement-id &#039;api_gateway&#039; \
        --principal apigateway.amazonaws.com \
        --source-arn &quot;arn:aws:execute-api:$AWS_DEFAULT_REGION:$ACCT_NO:$APIGW_ID/*/*/helloworld&quot;
</pre>
<p>Create an authorizer in the API Gateway.</p>
<pre class="brush: bash; title: ; notranslate">
AUTH_ID=$(aws apigateway create-authorizer --rest-api-id $APIGW_ID --name &#039;Authorizer1&#039; \
  --type REQUEST \
  --authorizer-uri &quot;arn:aws:apigateway:$AWS_DEFAULT_REGION:lambda:path/2015-03-31/functions/$LAMBDA_AUTH_ARN/invocations&quot;  \
  --authorizer-result-ttl-in-seconds 300 \
  --identity-source &#039;method.request.header.headerauth1&#039; \
  --output text --query &#039;id&#039;)
echo $AUTH_ID
</pre>
<p>Tell the API Gateway to use the authorizer.</p>
<pre class="brush: bash; title: ; notranslate">
aws apigateway update-method --rest-api-id $APIGW_ID --resource-id $RES_ID \
    --patch-operations &quot;op=replace,path=/authorizationType,value=CUSTOM&quot; \
    &quot;op=replace,path=/authorizerId,value=$AUTH_ID&quot; \
    --http-method ANY
</pre>
<p>Create a deployment and a stage.</p>
<pre class="brush: bash; title: ; notranslate">
STAGE=&quot;test&quot;
aws apigateway create-deployment --rest-api-id $APIGW_ID --stage-name $STAGE
</pre>
<p>Allow Lambda to be triggered by the Authorizer API Gateway.</p>
<pre class="brush: bash; title: ; notranslate">
aws lambda add-permission --function-name $LAMBDA_AUTH_ARN \
        --action lambda:InvokeFunction --statement-id &#039;api_gateway&#039; \
        --principal apigateway.amazonaws.com \
        --source-arn &quot;arn:aws:execute-api:$AWS_DEFAULT_REGION:$ACCT_NO:$APIGW_ID/authorizers/$AUTH_ID&quot;
</pre>
<p>Build the API Gateway URL.</p>
<pre class="brush: bash; title: ; notranslate">
APIURL=&quot;https://${APIGW_ID}.execute-api.${AWS_DEFAULT_REGION}.amazonaws.com/${STAGE}&quot;
echo $APIURL
</pre>
<p>Test without and with authorization.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl -X POST $APIURL/helloworld?name=there
{&quot;message&quot;:&quot;Unauthorized&quot;}
</pre>
<p>These are the parameters that we specified in line 27 in <strong>authorize.js</strong> file above.</p>
<pre class="brush: bash; title: ; notranslate">
curl -X POST $APIURL/helloworld?name=there -H &#039;headerauth1 : password&#039;
</pre>
<p>&#8230;and this is the JSON output.</p>
<pre class="brush: xml; collapse: true; light: false; title: ; toolbar: true; notranslate">
{
  &quot;message&quot;: &quot;Hello there, it&#039;s Sat Aug 19 2023 12:52:54 GMT+0000 (Coordinated Universal Time)&quot;,
  &quot;input&quot;: {
    &quot;resource&quot;: &quot;/helloworld&quot;,
    &quot;path&quot;: &quot;/helloworld&quot;,
    &quot;httpMethod&quot;: &quot;POST&quot;,
    &quot;headers&quot;: {
      &quot;accept&quot;: &quot;*/*&quot;,
      &quot;headerauth1&quot;: &quot;password&quot;,
      &quot;Host&quot;: &quot;p8d859ec50.execute-api.us-east-2.amazonaws.com&quot;,
      &quot;User-Agent&quot;: &quot;curl/7.76.1&quot;,
      &quot;X-Amzn-Trace-Id&quot;: &quot;Root=1-64e0bb26-5ea6f1225951c3d316e1de77&quot;,
      &quot;X-Forwarded-For&quot;: &quot;63.81.58.96&quot;,
      &quot;X-Forwarded-Port&quot;: &quot;443&quot;,
      &quot;X-Forwarded-Proto&quot;: &quot;https&quot;
    },
    &quot;multiValueHeaders&quot;: {
      &quot;accept&quot;: &#x5B;
        &quot;*/*&quot;
      ],
      &quot;headerauth1&quot;: &#x5B;
        &quot;password&quot;
      ],
      &quot;Host&quot;: &#x5B;
        &quot;p8d859ec50.execute-api.us-east-2.amazonaws.com&quot;
      ],
      &quot;User-Agent&quot;: &#x5B;
        &quot;curl/7.76.1&quot;
      ],
      &quot;X-Amzn-Trace-Id&quot;: &#x5B;
        &quot;Root=1-64e0bb26-5ea6f1225951c3d316e1de77&quot;
      ],
      &quot;X-Forwarded-For&quot;: &#x5B;
        &quot;63.81.58.96&quot;
      ],
      &quot;X-Forwarded-Port&quot;: &#x5B;
        &quot;443&quot;
      ],
      &quot;X-Forwarded-Proto&quot;: &#x5B;
        &quot;https&quot;
      ]
    },
    &quot;queryStringParameters&quot;: {
      &quot;name&quot;: &quot;there&quot;
    },
    &quot;multiValueQueryStringParameters&quot;: {
      &quot;name&quot;: &#x5B;
        &quot;there&quot;
      ]
    },
    &quot;pathParameters&quot;: null,
    &quot;stageVariables&quot;: null,
    &quot;requestContext&quot;: {
      &quot;resourceId&quot;: &quot;gter0y&quot;,
      &quot;authorizer&quot;: {
        &quot;numberKey&quot;: &quot;123&quot;,
        &quot;booleanKey&quot;: &quot;true&quot;,
        &quot;stringKey&quot;: &quot;stringval&quot;,
        &quot;principalId&quot;: &quot;me&quot;,
        &quot;integrationLatency&quot;: 0
      },
      &quot;resourcePath&quot;: &quot;/helloworld&quot;,
      &quot;httpMethod&quot;: &quot;POST&quot;,
      &quot;extendedRequestId&quot;: &quot;J6IuBFugiYcFq7Q=&quot;,
      &quot;requestTime&quot;: &quot;19/Aug/2023:12:52:54 +0000&quot;,
      &quot;path&quot;: &quot;/test/helloworld&quot;,
      &quot;accountId&quot;: &quot;261910724432&quot;,
      &quot;protocol&quot;: &quot;HTTP/1.1&quot;,
      &quot;stage&quot;: &quot;test&quot;,
      &quot;domainPrefix&quot;: &quot;p8d859ec50&quot;,
      &quot;requestTimeEpoch&quot;: 1692449574298,
      &quot;requestId&quot;: &quot;1e8461ad-e3ba-494c-8c19-169ce7a9a174&quot;,
      &quot;identity&quot;: {
        &quot;cognitoIdentityPoolId&quot;: null,
        &quot;accountId&quot;: null,
        &quot;cognitoIdentityId&quot;: null,
        &quot;caller&quot;: null,
        &quot;sourceIp&quot;: &quot;72.82.158.196&quot;,
        &quot;principalOrgId&quot;: null,
        &quot;accessKey&quot;: null,
        &quot;cognitoAuthenticationType&quot;: null,
        &quot;cognitoAuthenticationProvider&quot;: null,
        &quot;userArn&quot;: null,
        &quot;userAgent&quot;: &quot;curl/7.76.1&quot;,
        &quot;user&quot;: null
      },
      &quot;domainName&quot;: &quot;p8d859ec50.execute-api.us-east-2.amazonaws.com&quot;,
      &quot;apiId&quot;: &quot;p8d859ec50&quot;
    },
    &quot;body&quot;: null,
    &quot;isBase64Encoded&quot;: false
  }
}
</pre>
<h1>Create a secret in Secrets Manager</h1>
<p>We don&#8217;t want to store our parameters when we call the API in the code, so we&#8217;ll use Secrets Manager to keep the parameters (<em>headerauth1, password</em>) there.</p>
<pre class="brush: bash; title: ; notranslate">
SECRET_ARN=$(aws secretsmanager create-secret  --name &quot;helloworld/secret&quot; \
  --secret-string &quot;{\&quot;headerauth1\&quot;:\&quot;password\&quot;}&quot; --output text --query &#039;ARN&#039;)
echo $SECRET_ARN
</pre>
<h1>Create a DynamoDB table</h1>
<p>Let&#8217;s create a single DynamoDB table and add two records.</p>
<pre class="brush: bash; title: ; notranslate">
TABLE_ARN=$(aws dynamodb create-table \
  --table-name COMPUTERS \
  --attribute-definitions \
    AttributeName=COMPUTER,AttributeType=S \
    AttributeName=MEMORY_MB,AttributeType=N \
  --key-schema \
    AttributeName=COMPUTER,KeyType=HASH \
    AttributeName=MEMORY_MB,KeyType=RANGE \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
  --table-class STANDARD \
  --output text \
  --query &#039;TableDescription.TableArn&#039;)
echo $TABLE_ARN
</pre>
<p>Add some data.</p>
<pre class="brush: bash; title: ; notranslate">
aws dynamodb put-item \
 --table-name COMPUTERS \
 --item \
 &#039;{&quot;COMPUTER&quot;: {&quot;S&quot;: &quot;ZX SPECTRUM&quot;}, &quot;MEMORY_MB&quot;: {&quot;N&quot;: &quot;48&quot;}}&#039;

aws dynamodb put-item \
 --table-name COMPUTERS \
 --item \
 &#039;{&quot;COMPUTER&quot;: {&quot;S&quot;: &quot;COMMODORE&quot;}, &quot;MEMORY_MB&quot;: {&quot;N&quot;: &quot;64&quot;}}&#039;
</pre>
<h1>Create a VPC and EC2 instance</h1>
<p>We have to create a VPC where we&#8217;ll provision an Ubuntu EC2 instance and install Node.js on it. If you have a VPC and EC2 that you want to use, you can skip this step. For the sake of simplicity, the VPC will be with one public subnet. Make sure there is an output from each ECHO command. It means everything is OK.</p>
<pre class="brush: bash; title: ; notranslate">
# Create a VPC
VPCID=$(aws ec2 create-vpc --cidr-block 192.168.100.0/24 \
  --region us-east-2 \
  --tag-specifications &#039;ResourceType=&quot;vpc&quot;,Tags=&#x5B;{Key=&quot;Name&quot;,Value=&quot;demo&quot;}]&#039; \
  --output text --query &#039;Vpc.VpcId&#039;)
echo $VPCID

# Create an Internet Gateway
IGWID=$(aws ec2 create-internet-gateway --region us-east-2 \
  --output text --query &#039;InternetGateway.InternetGatewayId&#039;)
echo $IGWID

# Attach the Internet Gatewa to the VPC
aws ec2 attach-internet-gateway \
    --internet-gateway-id $IGWID \
    --vpc-id $VPCID

# Create a subnet
SUBID=$(aws ec2 create-subnet --vpc-id $VPCID \
  --tag-specifications &#039;ResourceType=&quot;subnet&quot;,Tags=&#x5B;{Key=&quot;Name&quot;,Value=&quot;subPublic&quot;}]&#039; \
  --cidr-block 192.168.100.0/24  \
  --output text --query &#039;Subnet.SubnetId&#039;)
echo $SUBID

# Get the main route ID that was created for the VPC
RTID=$(aws ec2 describe-route-tables \
  --filters Name=vpc-id,Values=$VPCID --output text \
  --query &#039;RouteTables&#x5B;*].RouteTableId&#039;)
echo $RTID

# Use the Internet Gateway for the Internet route 0.0.0.0/0
aws ec2 create-route --route-table-id $RTID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGWID

# Create a security group for the EC2 instance
SGID=$(aws ec2 create-security-group --group-name sgEC2 \
  --description &quot;Security group for the EC2 instance&quot; \
  --vpc-id $VPCID \
  --output text)
echo $SGID

# Get my home IP
MYIP=$(curl -s ifconfig.me)
echo $MYIP

# Allow SSH from my home IP
aws ec2 authorize-security-group-ingress \
    --group-id $SGID \
    --protocol tcp \
    --port 22 \
    --cidr $MYIP/32

# ... and port 3000 from my home IP again (used for Node.js app testing)
aws ec2 authorize-security-group-ingress \
    --group-id $SGID \
    --protocol tcp \
    --port 3000 \
    --cidr $MYIP/32

# Create a key pair
KEYNAME=&quot;keydemo&quot;
aws ec2 create-key-pair --key-name $KEYNAME \
  --query &#039;KeyMaterial&#039; \
  --output text &gt; ~/.ssh/$KEYNAME
chmod 0600 ~/.ssh/$KEYNAME

# Create an Ubuntu instance
IMGID=&quot;ami-024e6efaf93d85776&quot;

EC2ID=$(aws ec2 run-instances \
    --image-id $IMGID \
    --count 1 \
    --instance-type t2.small \
    --key-name $KEYNAME \
    --security-group-ids $SGID \
    --subnet-id $SUBID \
    --tag-specifications &#039;ResourceType=instance,Tags=&#x5B;{Key=Name,Value=ec2demo}]&#039; &#039;ResourceType=volume,Tags=&#x5B;{Key=Name,Value=volec2demo}]&#039; \
    --output text \
    --query &#039;Instances&#x5B;*].InstanceId&#039; \
    --associate-public-ip-address)

# Get the public IP
PUBLICIP=$(aws ec2 describe-instances --instance-ids $EC2ID \
  --output text \
  --query &#039;Reservations&#x5B;*].Instances&#x5B;*].NetworkInterfaces&#x5B;*].PrivateIpAddresses&#x5B;*].Association.PublicIp&#039;)
echo $PUBLICIP
</pre>
<p>Wait for a minute or two and SSH to the instance with:</p>
<pre class="brush: bash; title: ; notranslate">
ssh -i ~/.ssh/$KEYNAME ubuntu@$PUBLICIP
</pre>
<p><strong>Exit from the SSH session!!!</strong><br />
 We&#8217;ll need to create a role for the EC2 instance.</p>
<h1>Create an IAM role</h1>
<p>In order for the EC2 instance to access the DynamoDB and the Secrets Manager, we&#8217;ll create an IAM role and attach it to the EC2 instance. We don&#8217;t want to deal with IAM users and passwords, accessing the resources with a role is the proper way. Create a trust policy first.</p>
<pre class="brush: xml; title: ; notranslate">
cat &lt;&lt; EOF &gt; trust-policy.json
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;sts:AssumeRole&quot;
            ],
            &quot;Principal&quot;: {
                &quot;Service&quot;: &#x5B;
                    &quot;ec2.amazonaws.com&quot;
                ]
            }
        }
    ]
}
EOF
</pre>
<p>Then create two policies. One for DynamoDB access and the second one for access to Secrets Manager.</p>
<pre class="brush: xml; title: ; notranslate">
cat &lt;&lt; EOF &gt; polHelloWorldDynamoDB.json
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Sid&quot;: &quot;ListAndDescribe&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;dynamodb:List*&quot;,
                &quot;dynamodb:DescribeReservedCapacity*&quot;,
                &quot;dynamodb:DescribeLimits&quot;,
                &quot;dynamodb:DescribeTimeToLive&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Sid&quot;: &quot;SpecificTable&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;dynamodb:BatchGet*&quot;,
                &quot;dynamodb:DescribeStream&quot;,
                &quot;dynamodb:DescribeTable&quot;,
                &quot;dynamodb:Get*&quot;,
                &quot;dynamodb:Query&quot;,
                &quot;dynamodb:Scan&quot;,
                &quot;dynamodb:BatchWrite*&quot;,
                &quot;dynamodb:CreateTable&quot;,
                &quot;dynamodb:Delete*&quot;,
                &quot;dynamodb:Update*&quot;,
                &quot;dynamodb:PutItem&quot;
            ],
            &quot;Resource&quot;: &quot;$TABLE_ARN&quot;
        }
    ]
}
EOF
</pre>
<pre class="brush: bash; title: ; notranslate">
POL_DYNAMODB_ARN=$(aws iam create-policy --policy-name polHelloWorldDynamoDB \
  --policy-document file://polHelloWorldDynamoDB.json \
  --output text --query &#039;Policy.Arn&#039;)
echo $POL_DYNAMODB_ARN
</pre>
<pre class="brush: xml; title: ; notranslate">
cat &lt;&lt; EOF &gt; polHelloWorldSecretsManager.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:GetResourcePolicy&quot;,
                &quot;secretsmanager:GetSecretValue&quot;,
                &quot;secretsmanager:DescribeSecret&quot;,
                &quot;secretsmanager:ListSecretVersionIds&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;$SECRET_ARN&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;secretsmanager:ListSecrets&quot;,
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
EOF
</pre>
<pre class="brush: bash; title: ; notranslate">
POL_SECRETSMGR_ARN=$(aws iam create-policy --policy-name polHelloWorldSecretsManager \
  --policy-document file://polHelloWorldSecretsManager.json \
  --output text --query &#039;Policy.Arn&#039;)
echo $POL_SECRETSMGR_ARN
</pre>
<p>Then create a role called <strong>rolAccessToResources</strong> and attach the AWS policy that gives DynamoDB and Secrets Manager access.</p>
<pre class="brush: bash; title: ; notranslate">
ROLE_ACCESS_ARN=$(aws iam create-role --role-name rolAccessToResources \
  --assume-role-policy-document file://trust-policy.json --output text --query &#039;Role.Arn&#039;)
echo $ROLE_ACCESS_ARN
</pre>
<p>&#8230;attach the policies.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam attach-role-policy --policy-arn $POL_DYNAMODB_ARN --role-name rolAccessToResources
aws iam attach-role-policy --policy-arn $POL_SECRETSMGR_ARN --role-name rolAccessToResources
</pre>
<p>We need to attach this role to the EC2 instance.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-instance-profile --instance-profile-name ipnAccessToResources
</pre>
<p>&#8230;then</p>
<pre class="brush: bash; title: ; notranslate">
aws iam add-role-to-instance-profile --role-name rolAccessToResources --instance-profile-name ipnAccessToResources
</pre>
<p>Finally&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
aws ec2 associate-iam-instance-profile --instance-id $EC2ID --iam-instance-profile Name=ipnAccessToResources
</pre>
<h1>Create JavaScript files for testing</h1>
<p>Connect to the instance first.</p>
<pre class="brush: bash; title: ; notranslate">
ssh -i ~/.ssh/$KEYNAME ubuntu@$PUBLICIP
</pre>
<p>Install Node.js v18. Download and import the Nodesource GPG key</p>
<pre class="brush: bash; title: ; notranslate">
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
</pre>
<p>Then create the deb repo.</p>
<pre class="brush: bash; title: ; notranslate">
NODE_MAJOR=18
echo &quot;deb &#x5B;signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main&quot; | \
  sudo tee /etc/apt/sources.list.d/nodesource.list
</pre>
<p>Run update &#038; install.</p>
<pre class="brush: bash; title: ; notranslate">
sudo apt-get update
sudo apt-get install nodejs -y
</pre>
<p>Check.</p>
<pre class="brush: bash; title: ; notranslate">
node --version
</pre>
<p>Create a working folder.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir nodejs 
cd nodejs
npm install aws-sdk
</pre>
<p>This is a script that will dump the DynamoDB table.</p>
<pre class="brush: jscript; title: ; notranslate">
cat &lt;&lt; EOF &gt; scan.js
&#039;use strict&#039;;
// Load the AWS SDK for Node.js
var AWS = require(&#039;aws-sdk&#039;);
// Set the region 
AWS.config.update({region: &#039;us-east-2&#039;});

// Create the DynamoDB service object
var ddb = new AWS.DynamoDB({apiVersion: &#039;2012-08-10&#039;});

var params = {
  TableName: &#039;COMPUTERS&#039;
};

// Call DynamoDB to add the item to the table
ddb.scan(params, function(err, data) {
  if (err) {
    console.log(&quot;Error&quot;, err);
  } else {
    console.log(&quot;Success&quot;, data.Items);
  }
});
EOF
</pre>
<p>Check if everything is there.</p>
<pre class="brush: bash; title: ; notranslate">
node scan.js
</pre>
<p>You&#8217;ll get a response like this.</p>
<pre class="brush: xml; title: ; notranslate">
Success &#x5B;
  { MEMORY_MB: { N: &#039;48&#039; }, COMPUTER: { S: &#039;ZX SPECTRUM&#039; } },
  { MEMORY_MB: { N: &#039;64&#039; }, COMPUTER: { S: &#039;COMMODORE&#039; } }
]
</pre>
<p>This is a script that will check the API Gateway/Lambda. <strong>Replace the URL at line 7 with the value of $APIURL from your local computer + add the header. </strong></p>
<pre class="brush: jscript; highlight: [7]; title: ; notranslate">
cat &lt;&lt; EOF &gt; fetch.js
&#039;use strict&#039;;

(async () =&gt; {

        const requestOptions = { method: &#039;POST&#039;, headers: { &#039;headerauth1&#039;:&#039;password&#039; } };
        const url = &#039;https://p8d859ec50.execute-api.us-east-2.amazonaws.com/test/helloworld?name=there&#039;;

        const response = await fetch(url, requestOptions);
        //console.log(response);
        const data = await response.json();
        console.log(data.message);
})();
EOF
</pre>
<p>Test the API.</p>
<pre class="brush: bash; title: ; notranslate">
node fetch.js
</pre>
<p>The output should be something like this.</p>
<pre class="brush: plain; title: ; notranslate">
Hello there, it&#039;s Sat Aug 19 2023 22:06:22 GMT+0000 (Coordinated Universal Time)
</pre>
<p>This is a script that will retrieve the secrets from the Secrets Manager.</p>
<pre class="brush: jscript; title: ; notranslate">
cat &lt;&lt; EOF &gt; secret.js
const AWS = require(&#039;aws-sdk&#039;);
const client = new AWS.SecretsManager({ region: &quot;us-east-2&quot; });

const getMySecret = async (SecretId) =&gt; {
  const s = await client.getSecretValue({ SecretId }).promise();
  return s.SecretString;
};

(async() =&gt; {
  const secret_101 = await getMySecret(&#039;helloworld/secret&#039;);
  console.log(&#039;My secret:&#039;, secret_101);
})();
EOF
</pre>
<p>And if you execute you&#8217;ll get the output. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
node secret.js
My secret: {&quot;headerauth1&quot;:&quot;password&quot;}
</pre>
<p>The final code that combines all is this one. <strong>Replace the URL at line 35 with the value of $APIURL.</strong></p>
<pre class="brush: jscript; title: ; notranslate">
cat &lt;&lt; &#039;EOF&#039; &gt; index.js
&#039;use strict&#039;;

const AWS = require(&#039;aws-sdk&#039;);
AWS.config.update({region: &#039;us-east-2&#039;});
const ddb = new AWS.DynamoDB({apiVersion: &#039;2012-08-10&#039;});
const client = new AWS.SecretsManager({ region: &quot;us-east-2&quot; });
 
var params = {
  TableName: &#039;COMPUTERS&#039;
};

const getMySecret = async (SecretId) =&gt; {
  const s = await client.getSecretValue({ SecretId }).promise();
  return s.SecretString;
};
 
var mysecret = &#039;&#039;;
 
params = {
  TableName: &#039;COMPUTERS&#039;
};
 
ddb.scan(params, function(err, data) {
  if (err) {
    console.log(&quot;Error&quot;, err);
  } else {
    console.log(&quot;Success&quot;, data.Items);
  }
});
 
(async () =&gt; {
        mysecret = await getMySecret(&#039;helloworld/secret&#039;);
        var requestOptions = { method: &#039;POST&#039;, headers: JSON.parse(mysecret) };
        const url = &#039;https://3utbhdiri4.execute-api.us-east-2.amazonaws.com/test/helloworld?name=there&#039;;
 
        const response = await fetch(url, requestOptions);
        const data = await response.json();
        console.log(data.message);
})();
EOF
</pre>
<p>&#8230;and the final output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
node index.js
Success &#x5B;
  { MEMORY_MB: { N: &#039;48&#039; }, COMPUTER: { S: &#039;ZX SPECTRUM&#039; } },
  { MEMORY_MB: { N: &#039;64&#039; }, COMPUTER: { S: &#039;COMMODORE&#039; } }
]
Success &#x5B;
  { MEMORY_MB: { N: &#039;48&#039; }, COMPUTER: { S: &#039;ZX SPECTRUM&#039; } },
  { MEMORY_MB: { N: &#039;64&#039; }, COMPUTER: { S: &#039;COMMODORE&#039; } }
]
Hello there, it&#039;s Wed Sep 20 2023 20:02:29 GMT+0000 (Coordinated Universal Time)
</pre>
<p>As you can see, I don&#8217;t have any secrets/passwords in the code and it&#8217;s still working. That&#8217;s because I am getting the secret at line 33 and pass it in line 34.</p>
<h1>Dockerizing the app</h1>
<p>If we want to run this app as a container under EKS, we have to make sure it runs as a Node.js service and then dockerize it.<br />
The Express version of the same app is this. I am far from an expert for Node.js/Express, so I&#8217;ll give it a best shot.<br />
While you are at the nodejs folder, install Express.</p>
<pre class="brush: bash; title: ; notranslate">
npm install express
</pre>
<p>Then&#8230;<strong>Make sure you replace line 41 with the $APIURL from your local computer!!!</strong></p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;&#039;EOF&#039; &gt; server.js
&#039;use strict&#039;;
const express = require(&#039;express&#039;)
const app = express()
const port = 3000

const AWS = require(&#039;aws-sdk&#039;);
AWS.config.update({region: &#039;us-east-2&#039;});
const ddb = new AWS.DynamoDB({apiVersion: &#039;2012-08-10&#039;});
const client = new AWS.SecretsManager({ region: &quot;us-east-2&quot; });

var params = {
  TableName: &#039;COMPUTERS&#039;
};

const getMySecret = async (SecretId) =&gt; {
  const s = await client.getSecretValue({ SecretId }).promise();
  return s.SecretString;
};

var mysecret = &#039;&#039;;
var dbitems = &#039;&#039;;

params = {
  TableName: &#039;COMPUTERS&#039;
};

ddb.scan(params, function(err, data) {
  if (err) {
    console.log(&quot;Error&quot;, err);
  } else {
    console.log(&quot;Success&quot;, data.Items);
    dbitems = data.Items;
  }
});

var data1 = &#039;&#039;;
(async () =&gt; {
        mysecret = await getMySecret(&#039;helloworld/secret&#039;);
        var requestOptions = { method: &#039;POST&#039;, headers: JSON.parse(mysecret) };
        const url = &#039;https://3utbhdiri4.execute-api.us-east-2.amazonaws.com/test/helloworld?name=there&#039;;

        const response = await fetch(url, requestOptions);
        data1 = await response.json();
        console.log(data1);
})();

app.get(&#039;/&#039;, (req, res) =&gt; {
    res.send(JSON.stringify(dbitems) + &quot; &quot; + data1.message);
})

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})
EOF
</pre>
<p>Run this application with <strong>node server.js</strong> and the app will listen on port 3000. Open a new session to the terminal and go to the public IP of the instance https://$PUBLICIP:3000 and you&#8217;ll see something like this. Or just open up a browser and go to $PUBLICIP:3000. We opened port 3000 earlier on the EC2 instance for this reason. <strong>Replace the $PUBLICIP with the public IP of the EC2 Ubuntu instance.</strong></p>
<pre class="brush: bash; title: ; notranslate">
curl $PUBLICIP:3000
</pre>
<p>CTRL-C out of the app and let&#8217;s install Docker.</p>
<pre class="brush: bash; title: ; notranslate">
sudo apt install -y docker.io
</pre>
<p>Create a <strong>Dockerfile</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; Dockerfile 
FROM node:18
 
# 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; ]
EOF
</pre>
<p>Create a <strong>.dockerignore</strong> file.</p>
<pre class="brush: plain; title: ; notranslate">
cat &lt;&lt;EOF &gt; .dockerignore
node_modules
npm-debug.log
EOF
</pre>
<p>Now, we can create the image and push to Dockerhub. Use your own repo here.</p>
<pre class="brush: bash; title: ; notranslate">
sudo docker build . -t klimenta/awsdemo
sudo docker images
sudo docker run -p 3000:3000 -d klimenta/awsdemo
</pre>
<p>GO to the same $PUBLIC of the Ubuntu and you&#8217;ll see the same output but this time from the Docker container.<br />
Push the image to the Dockerhub.</p>
<pre class="brush: bash; title: ; notranslate">
sudo docker login
sudo docker push kliment/awsdemo
</pre>
<h1>Run as a container in EKS cluster</h1>
<p>Now that we have the container and we know it&#8217;s working fine, let&#8217;s provision an EKS cluster and deploy our Docker image.<br />
Do this from your computer, not the Ubuntu server in AWS.</p>
<pre class="brush: bash; title: ; notranslate">
CLUSTER_NAME=&quot;eksAWSDemo&quot;
eksctl create cluster --name $CLUSTER_NAME --region us-east-2 --instance-types t3.medium --nodes 2 --managed --version 1.27
</pre>
<p>And this is our deployment that creates a public load balancer so we can access the deployment.</p>
<pre class="brush: yaml; title: ; notranslate">
cat &lt;&lt;EOF &gt; demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      containers:
      - name: demo
        image: klimenta/awsdemo
        ports:
        - containerPort: 3000
EOF
</pre>
<p>Deploy the solution.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl apply -f demo.yaml
deployment.apps/demo created
service/loadbalancer created
</pre>
<p>And you&#8217;ll get this if you want to check the container(pod).</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                    READY   STATUS   RESTARTS      AGE
demo-5655f58d9d-z9pnz   0/1     Error    2 (17s ago)   20s
</pre>
<p>It errored out.<br />
&#8230;and if you want to see why the container crashed, it is very obvious.</p>
<pre class="brush: bash; highlight: [3,27]; title: ; notranslate">
kubectl logs demo-5655f58d9d-z9pnz
(Use `node --trace-warnings ...` to show where the warning was created)
Error AccessDeniedException: User: arn:aws:sts::261123456789:assumed-role/eksctl-eksDemoAWS-nodegroup-ng-0c-NodeInstanceRole-UUU04JSKQOJR/i-09df331c5f755bc7e is not authorized to perform: dynamodb:Scan on resource: arn:aws:dynamodb:us-east-2:261910724432:table/COMPUTERS because no identity-based policy allows the dynamodb:Scan action
    at Request.extractError (/usr/src/app/node_modules/aws-sdk/lib/protocol/json.js:80:27)
    at Request.callListeners (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/request.js:686:14)
    at Request.transition (/usr/src/app/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/usr/src/app/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /usr/src/app/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.&lt;anonymous&gt; (/usr/src/app/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.&lt;anonymous&gt; (/usr/src/app/node_modules/aws-sdk/lib/request.js:688:12)
    at Request.callListeners (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
  code: &#039;AccessDeniedException&#039;,
  &#039;&#x5B;__type]&#039;: &#039;See error.__type for details.&#039;,
  &#039;&#x5B;Message]&#039;: &#039;See error.Message for details.&#039;,
  time: 2023-09-21T20:46:01.784Z,
  requestId: &#039;SDK8R0LSTASAIIBIO3UUU1RC3RVV4KQNSO5AEMVJF66Q9ASUAAJG&#039;,
  statusCode: 400,
  retryable: false,
  retryDelay: 32.90504606038562
}
/usr/src/app/node_modules/aws-sdk/lib/protocol/json.js:80
  resp.error = util.error(new Error(), error);
                          ^

AccessDeniedException: User: arn:aws:sts::261987654321:assumed-role/eksctl-eksDemoAWS-nodegroup-ng-0c-NodeInstanceRole-UUU04JSKQOJR/i-09df331c5f755bc7e is not authorized to perform: secretsmanager:GetSecretValue on resource: helloworld/secret because no identity-based policy allows the secretsmanager:GetSecretValue action
    at Request.extractError (/usr/src/app/node_modules/aws-sdk/lib/protocol/json.js:80:27)
    at Request.callListeners (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/usr/src/app/node_modules/aws-sdk/lib/request.js:686:14)
    at Request.transition (/usr/src/app/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/usr/src/app/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /usr/src/app/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.&lt;anonymous&gt; (/usr/src/app/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.&lt;anonymous&gt; (/usr/src/app/node_modules/aws-sdk/lib/request.js:688:12)
    at Request.callListeners (/usr/src/app/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
  code: &#039;AccessDeniedException&#039;,
  &#039;&#x5B;__type]&#039;: &#039;See error.__type for details.&#039;,
  &#039;&#x5B;Message]&#039;: &#039;See error.Message for details.&#039;,
  time: 2023-09-21T20:46:01.800Z,
  requestId: &#039;035cbe99-6826-4f46-b4f8-115cfa90d88b&#039;,
  statusCode: 400,
  retryable: false,
  retryDelay: 58.98522014520893
}
</pre>
<p>We created a role (<strong>rolAccessToResources</strong>) and two policies (<strong>polHelloWorldDynamoDB </strong>and <strong>polHelloWorldSecretsManager</strong>) to allow access to the AWS resources, but we assigned them to the Ubuntu instance. Now, the EKS cluster and the nodes are trying to do the same with their default role (<strong>eksctl-eksDemoAWS-nodegroup-ng-0c-NodeInstanceRole-UUU04JSKQOJR</strong>), but that role has no access to the DynamoDB table and Secrets Manager, hence the error.<br />
Giving access to the resources to this EKS role is not recommended because all the nodes will have access and we might have other apps running in this cluster.<br />
So we need to give access to the pod, its replicas and all new pods in case the original pods crashes or it&#8217;s removed by the autoscaler. </p>
<h1>Grant access to AWS resources for a pod</h1>
<p>This is a three steps process. We&#8217;ll have to:</p>
<ul>
<li>Create an IAM OIDC provider</li>
<li>Configure the role and a service account</li>
<li>Configure the pods</li>
</ul>
<p>Let&#8217;s do one by one.</p>
<h2>Create an IAM OIDC provider</h2>
<p>Let&#8217;s determine whether we have an existing IAM OIDC provider for our cluster.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws eks describe-cluster --name eksAWSDemo --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 <em>eksctl</em>, 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; title: ; notranslate">
OIDCID=$(aws eks describe-cluster --name $CLUSTER_NAME --query &quot;cluster.identity.oidc.issuer&quot; --output text | cut -d &#039;/&#039; -f 5)
echo $OIDCID
</pre>
<p>Check if the IAM OIDC provider is already configured. It shouldn&#8217;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.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
eksctl utils associate-iam-oidc-provider --cluster $CLUSTER_NAME --approve
2023-09-22 12:27:47 &#x5B;ℹ]  will create IAM Open ID Connect provider for cluster &quot;eksAWSDemo&quot; in &quot;us-east-2&quot;
2023-09-22 12:27:47 &#x5B;✔]  created IAM Open ID Connect provider for cluster &quot;eksAWSDemo&quot; in &quot;us-east-2&quot;
</pre>
<h2>Configure the role and a service account</h2>
<p>We have the role created, that&#8217;s <strong>rolAccessToResources</strong>, that we did initially. This role has two policies that give us access to DynamoDB and Secrets Manager. All we have to do is to create the service account. You can&#8217;t use uppercase in the name of the service account.</p>
<pre class="brush: bash; highlight: [1,2,3]; title: ; notranslate">
SA_NAME=&quot;sapodshelloworld&quot;
eksctl create iamserviceaccount --name $SA_NAME --namespace default \
  --cluster $CLUSTER_NAME  --attach-role-arn $ROLE_ACCESS_ARN --approve
2023-09-22 12:44:50 &#x5B;ℹ]  1 iamserviceaccount (default/sapodshelloworld) was included (based on the include/exclude rules)
2023-09-22 12:44:50 &#x5B;!]  serviceaccounts that exist in Kubernetes will be excluded, use --override-existing-serviceaccounts to override
2023-09-22 12:44:50 &#x5B;ℹ]  1 task: { create serviceaccount &quot;default/sapodshelloworld&quot; }
2023-09-22 12:44:50 &#x5B;ℹ]  created serviceaccount &quot;default/sapodshelloworld&quot;
</pre>
<p>Check the service account.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get sa
NAME               SECRETS   AGE
default            0         93m
sapodshelloworld   0         27m
</pre>
<p>Check if the role has the proper policies.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws iam list-attached-role-policies --role-name rolAccessToResources --query AttachedPolicies&#x5B;].PolicyArn
&#x5B;
    &quot;arn:aws:iam::261123456789:policy/polHelloWorldSecretsManager&quot;,
    &quot;arn:aws:iam::261987654321:policy/polHelloWorldDynamoDB&quot;
]
</pre>
<p>In addition, we also have to change the trust policy for our role <strong>rolAccessToResources</strong>. Currently, it looks like this.</p>
<pre class="brush: xml; title: ; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: {
                &quot;Service&quot;: &quot;ec2.amazonaws.com&quot;
            },
            &quot;Action&quot;: &quot;sts:AssumeRole&quot;
        }
    ]
}
</pre>
<p>This is the proper access policy.</p>
<pre class="brush: bash; title: ; notranslate">
cat&lt;&lt;EOF &gt; trust-policy.json
{
    &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::$ACCT_NO:oidc-provider/oidc.eks.$AWS_DEFAULT_REGION.amazonaws.com/id/$OIDCID&quot;
            },
            &quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;oidc.eks.$AWS_DEFAULT_REGION.amazonaws.com/id/$OIDCID:aud&quot;: &quot;sts.amazonaws.com&quot;,
                    &quot;oidc.eks.$AWS_DEFAULT_REGION.amazonaws.com/id/$OIDCID:sub&quot;: &quot;system:serviceaccount:default:$SA_NAME&quot;
                }
            }
        }
    ]
}
EOF
</pre>
<p>Modify the role&#8217;s trust policy.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam update-assume-role-policy --role-name rolAccessToResources --policy-document file://trust-policy.json
</pre>
<p>Check if correct.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam get-role --role-name rolAccessToResources --query Role.AssumeRolePolicyDocument
</pre>
<p>Check the service account and the role.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl describe serviceaccount $SA_NAME -n default | grep Anno
Annotations:         eks.amazonaws.com/role-arn: arn:aws:iam::261987654321:role/rolAccessToResources
</pre>
<h2>Configure the pods</h1>
<p>Actually, we don&#8217;t need to configure the pods, we have to configure the deployment that creates the pods.<br />
The only change that you have to make is to specify the service account in the YAML file.<br />
Kill the pods if you have them running.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete -f demo.yaml
</pre>
<p>Create a new deployment <em>demo.yaml</em> file. You only have to add <strong>one </strong>line (line 16 to specify the service account) + a part with the load balancer.</p>
<pre class="brush: yaml; highlight: [16]; title: ; notranslate">
cat&lt;&lt;EOF &gt; demo-new.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      serviceAccountName: sapodshelloworld 
      containers:
      - name: demo
        image: klimenta/awsdemo
        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: demo
EOF
</pre>
<p>Deploy the solution again.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo-new.yaml
</pre>
<p>Get the ULR of the load balancer.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get svc
</pre>
<p>&#8230;and if you go to that URL after 2-3 mins, you&#8217;ll see that everthing works as expected.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl ab4fdd725967a438ea5a10b4866b720b-b76f9507fafb4eed.elb.us-east-2.amazonaws.com
&#x5B;{&quot;MEMORY_MB&quot;:{&quot;N&quot;:&quot;48&quot;},&quot;COMPUTER&quot;:{&quot;S&quot;:&quot;ZX SPECTRUM&quot;}},{&quot;MEMORY_MB&quot;:{&quot;N&quot;:&quot;64&quot;},&quot;COMPUTER&quot;:{&quot;S&quot;:&quot;COMMODORE&quot;}}] Hello there, it&#039;s Fri Sep 22 2023 18:49:49 GMT+0000 (Coordinated Universal Time)
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/09/aws-access-dynamodb-and-secrets-manager-from-node-js-on-ec2-and-eks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: EKS running out of IPs, increase pod density</title>
		<link>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/</link>
					<comments>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 06 Jun 2023 21:04:16 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Calico]]></category>
		<category><![CDATA[CNI]]></category>
		<category><![CDATA[EKS]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9358</guid>

					<description><![CDATA[When I was doing some proof of concept using EKS, I&#8217;ve noticed that there&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>When I was doing some proof of concept using EKS, I&#8217;ve noticed that there is a limitation on how many pods can a node run and how EKS CNI assigns the subnet IPs. Apparently, a node can run a <a href="https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt" rel="noopener" target="_blank">maximum of XX pods </a>(based on type) which is very low, e.g. a t3.large instance with 2 CPUs and 8GB RAM can run only 35 pods. On top of that, the IPs that are assigned for the pods are used from the existing pool of IPs and if you have a /24 subnet for the EKS cluster, you&#8217;ll run out of IPs in no time. Here is an example of what I did and options how to solve this issue.</p>
<h1>Option 1 &#8211; Calico 3rd party CNI</h1>
<p>I&#8217;ll provision an EKS cluster in a /24 subnet with 3 public and 3 private subnets.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksECIC --region us-east-2 --instance-types t3.large \
    --managed --vpc-cidr 192.168.100.0/24 --node-private-networking --version 1.24 --without-nodegroup
</pre>
<p>I am using the Ohio region which has 3 availability zones. The public and private subnets (total of 6) have /27 subnet which is 32 IPs. Not all of them are usable of course. Let&#8217;s create a node group with 3 nodes, and because <em>t3.large</em> is a Nitro instance, we can set the <a href="https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html" rel="noopener" target="_blank">max number of pods</a> to be 110.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name old-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>These are the available 31 IPs that I have in the private subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1024x201.png" alt="" width="1024" height="201" class="aligncenter size-large wp-image-9371" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1024x201.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-300x59.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-768x151.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1170x230.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-585x115.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01.png 1211w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s provision a small deployment with a single pod and 10 replicas and a network load balancer.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>&#8230;and this is the deployment file <strong>demo.yaml</strong>.</p>
<pre class="brush: yaml; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 10
  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
  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: demo
</pre>
<p>If you look at the available address now, there are 7. So from 31 to 7 with just 10 pods. Check this guy&#8217;s <a href="https://medium.com/codex/kubernetes-cluster-running-out-of-ip-addresses-on-aws-eks-c7b8e5dd8606" rel="noopener" target="_blank">blog </a>to see what&#8217;s going on.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1024x116.png" alt="" width="1024" height="116" class="aligncenter size-large wp-image-9372" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1024x116.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-300x34.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-768x87.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1170x133.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-585x66.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02.png 1173w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s scale up the cluster to 100 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=100 deployment/demo
</pre>
<p>After a minute or two, check the running pods.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
69
</pre>
<p>The console will show 0 available subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1024x109.png" alt="" width="1024" height="109" class="aligncenter size-large wp-image-9373" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1024x109.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-300x32.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-768x82.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1170x125.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-585x62.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03.png 1212w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you check the pods with <strong>kubectl get pods</strong>, you&#8217;ll see the remaining containers have a <em>ContainerCreating </em>status.<br />
And if you check one of them, you&#8217;ll see this message.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl describe pod demo-698f6fc958-z8smb
....
d9b65ccfeccb7e9e7433ec61fef78faf83d1bae&quot;: plugin type=&quot;aws-cni&quot; name=&quot;aws-cni&quot; failed (add): add cmd: failed to assign an IP address to container
  Warning  FailedCreatePodSandBox  2m29s (x17 over 5m59s)  kubelet            (combined from similar events): Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox &quot;edfa987c3eddf415068691f914d740fa9ca9ca6ffdfb8db44d1340d913ece0b0&quot;: plugin type=&quot;aws-cni&quot; name=&quot;aws-cni&quot; failed (add): add cmd: failed to assign an IP address to container
</pre>
<p>Get 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.100.0.1      &lt;none&gt;                                                                          443/TCP        37m
loadbalancer   LoadBalancer   10.100.25.241   a105789cd3a264ac596c367fce463640-80d2b88acdc17771.elb.us-east-2.amazonaws.com   80:32396/TCP   17m
</pre>
<p>If you go to <em>a105789cd3a264&#8230;amazonaws.com</em> URL, you&#8217;ll see the load balancer hitting different pods. Wait for 2-3 minutes if you see that the page can&#8217;t be opened. It takes time for DNS to propagate.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1024x355.png" alt="" width="1024" height="355" class="aligncenter size-large wp-image-9374" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1024x355.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-300x104.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-768x266.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1170x406.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-585x203.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04.png 1470w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s delete the deployment and the old node group.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete -f demo.yaml
eksctl delete nodegroup --name old-nodegroup --cluster eksECIC
</pre>
<p>At this point, we still have the EKS cluster, the etcd database, networking etc, we just don&#8217;t have the nodes. Now it&#8217;s time to remove the EKS CNI, install Calico and add nodes that will use Calico.<br />
Installation is very simple. Remove the AWS CNI.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete daemonset -n kube-system aws-node
</pre>
<p>Check the pods in all namespaces.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods --all-namespaces
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   coredns-5c5677bc78-2k88b   0/1     Pending   0          4m
kube-system   coredns-5c5677bc78-2zz2n   0/1     Pending   0          4m
</pre>
<p>You will see <strong>coredns </strong>in pending state, it can&#8217;t be deployed anywhere.<br />
Deploy Calico.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/tigera-operator.yaml
</pre>
<p>&#8230;keep going&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f - &lt;&lt;EOF
kind: Installation
apiVersion: operator.tigera.io/v1
metadata:
  name: default
spec:
  kubernetesProvider: EKS
  cni:
    type: Calico
  calicoNetwork:
    bgp: Disabled
EOF
</pre>
<p>Deploy the new node group.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name new-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>Let&#8217;s deploy the same demo deployment now.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>&#8230;scale it up to 601 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=601 deployment/demo
</pre>
<p>Wait 2-3 mins and check the running ones.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
</pre>
<p>There will be 585 pods running, 195 per node. Much, much better. This time the remaining containers will be in <em>Pending </em>state.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl describe pod demo-698f6fc958-smlbq
....
Events:
  Type     Reason            Age                  From               Message
  ----     ------            ----                 ----               -------
  Warning  FailedScheduling  3m27s (x3 over 13m)  default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
</pre>
<h1>Option 2 &#8211; CNI Custom networking and prefix assignment mode</h1>
<p>In this case, we&#8217;ll keep the AWS CNI network plugin, but we&#8217;ll use what is called CNI Custom networking, meaning we&#8217;ll use another CIDR attached to the VPC where pods will run. NOTE: The nodes will still run in the 192.168/24 subnets, it&#8217;s just the pods that will run in a much bigger IP space. In addition, this CNI Custom networking won&#8217;t solve the density of the pods that we can run on the nodes. We&#8217;ll need something called prefix assignment mode that will allow us to run much more pods on a node than usual. See <a href="https://aws.amazon.com/blogs/containers/leveraging-cni-custom-networking-alongside-security-groups-for-pods-in-amazon-eks/" rel="noopener" target="_blank">this </a>and <a href="https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html" rel="noopener" target="_blank">this </a>links for more information. For more info regarding prefix assignment mode check out <a href="https://aws.amazon.com/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/" rel="noopener" target="_blank">this </a>link. The solution described below is much more complicated than if you just use Calico.<br />
Let&#8217;s provision a cluster with the same VPC CIDR and no nodegroups.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksECIC --region us-east-2 --instance-types t3.large \
    --managed --vpc-cidr 192.168.100.0/24 --node-private-networking --version 1.24 --without-nodegroup
</pre>
<p>Add a new CIDR and associate it with the VPC. I am using the console but you can use <em>aws ec2 associate-vpc-cidr-block</em> command.Go to the VPC where the EKS cluster resides. From the <strong>Actions </strong>button, select <strong>Edit CIDRs</strong> and then click the <strong>Add new IPv4 CIDR</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png" alt="" width="982" height="511" class="aligncenter size-full wp-image-9386" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png 982w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-300x156.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-768x400.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-585x304.png 585w" sizes="(max-width: 982px) 100vw, 982px" /></a><br />
I&#8217;ll add <strong>100.64.0.0/16</strong> CIDR and then I&#8217;ll create 3 /19 subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1024x124.png" alt="" width="1024" height="124" class="aligncenter size-large wp-image-9387" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1024x124.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-300x36.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-768x93.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1536x185.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1170x141.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-585x71.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06.png 1789w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
These 3 subnets will be where the pods will run. I also want them to initiate connections to Internet, so I&#8217;ll modify the route table and add the Internet Gateway. The three new subnets 100.64/19 that we created have the same route table.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1024x287.png" alt="" width="1024" height="287" class="aligncenter size-large wp-image-9388" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1024x287.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-768x216.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1170x328.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-585x164.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07.png 1350w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Just add the route to 0.0.0.0/0 to go over Internet Gateway.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1024x261.png" alt="" width="1024" height="261" class="aligncenter size-large wp-image-9389" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1024x261.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-300x77.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-768x196.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1170x299.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-585x149.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08.png 1528w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s configure the custom networking on the cluster.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
</pre>
<p>Get the EKS cluster security group.</p>
<pre class="brush: bash; title: ; notranslate">
sec_grp=$(aws eks describe-cluster --name eksECIC --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)
echo $sec_grp
</pre>
<p>For each of the subnets create a custom resource. Define the three 100.64 subnet-ids as variables.</p>
<pre class="brush: bash; title: ; notranslate">
export sub1=subnet-0f3ec5d75caf1b981
export sub2=subnet-0fc39edc5a1ce91c0
export sub3=subnet-0bd6bdfd5543aa086
</pre>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2a.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2a
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub1
EOF
</pre>
<p>2nd subnet.</p>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2b.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2b
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub2
EOF
</pre>
<p>3rd subnet.</p>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2c.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2c
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub3
EOF
</pre>
<p><strong>NOTE:</strong>Make sure you name the metadata in line 5 as I did based on your region and AZ.<br />
Deploy the custom resources.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f us-east-2a.yaml
kubectl apply -f us-east-2b.yaml
kubectl apply -f us-east-2c.yaml
</pre>
<p>Confirm it looks good.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get ENIConfigs
NAME         AGE
us-east-2a   16s
us-east-2b   12s
us-east-2c   8s
</pre>
<p>Update your aws-node DaemonSet to automatically apply the ENIConfig for an Availability Zone to any new Amazon EC2 nodes created in your cluster.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone
</pre>
<p>Enable prefix assignment mode. This will allow much more pods per node.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
</pre>
<p>Another change.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env ds aws-node -n kube-system WARM_PREFIX_TARGET=1
</pre>
<p>With the default setting, WARM_PREFIX_TARGET will allocate one additional complete (/28) prefix even if the existing prefix is used by only one pod. If the ENI does not have enough space to assign a prefix, a new ENI is generated.<br />
Provision the node group.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name my-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>Check the nodes, they are running in the 192.168/24 original VPC CIDR. See the IP in the names of the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                                            STATUS   ROLES    AGE     VERSION
ip-192-168-100-122.us-east-2.compute.internal   Ready    &lt;none&gt;   7m39s   v1.24.13-eks-0a21954
ip-192-168-100-155.us-east-2.compute.internal   Ready    &lt;none&gt;   7m39s   v1.24.13-eks-0a21954
ip-192-168-100-167.us-east-2.compute.internal   Ready    &lt;none&gt;   7m37s   v1.24.13-eks-0a21954
</pre>
<p>Check the pods.</p>
<pre class="brush: bash; highlight: [1,6,7]; title: ; notranslate">
kubectl get pods -A -o wide
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE    IP                NODE                                            NOMINATED NODE   READINESS GATES
kube-system   aws-node-txwrd             1/1     Running   0          9m4s   192.168.100.167   ip-192-168-100-167.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   aws-node-vdmv7             1/1     Running   0          9m6s   192.168.100.155   ip-192-168-100-155.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   aws-node-xss7c             1/1     Running   0          9m6s   192.168.100.122   ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-5c5677bc78-2lkcx   1/1     Running   0          80m    100.64.76.225     ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-5c5677bc78-mj6wr   1/1     Running   0          80m    100.64.76.224     ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-lt7v4           1/1     Running   0          9m6s   192.168.100.122   ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-m8pf6           1/1     Running   0          9m4s   192.168.100.167   ip-192-168-100-167.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-nlvw7           1/1     Running   0          9m6s   192.168.100.155   ip-192-168-100-155.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
</pre>
<p>Look at <strong>coredns </strong>pods. They are running in the new CIDR.<br />
Deploy the demo.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>Get 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.100.0.1       &lt;none&gt;                                                                          443/TCP        83m
loadbalancer   LoadBalancer   10.100.206.187   a7ad3ecf1d52e4fc58dbbdc550237009-ac4778e11af2c0ad.elb.us-east-2.amazonaws.com   80:30230/TCP   45s
</pre>
<p>If you go to <em>a7ad3ecf1d&#8230;amazonaws.com</em> URL, you&#8217;ll see the load balancer hitting different pods. Wait for 2-3 minutes if you see that the page can&#8217;t be opened. It takes time for DNS to propagate.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1024x460.png" alt="" width="1024" height="460" class="aligncenter size-large wp-image-9401" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1024x460.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-300x135.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-768x345.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1170x526.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-585x263.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09.png 1531w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
The pods are running in the 100.64 subnet.<br />
Scale up to 601 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=601 deployment/demo
</pre>
<p>Wait 2-3 mins and check the running ones.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
</pre>
<p>I got 593 total pods. Slightly better than 585 with Calico. The ones that are in pending state show up an error that there are no more resources.</p>
<pre class="brush: bash; title: ; notranslate">
Events:
  Type     Reason            Age    From               Message
  ----     ------            ----   ----               -------
  Warning  FailedScheduling  3m49s  default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: EKS monitoring and logging</title>
		<link>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/</link>
					<comments>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 03 Oct 2022 23:09:44 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[CloudWatch]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[fluentbit]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[logging]]></category>
		<category><![CDATA[monitoring]]></category>
		<category><![CDATA[prometheus]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9214</guid>

					<description><![CDATA[In this post I&#8217;ll explain several things related to EKS monitoring and logging. &#8211;&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain several things related to EKS monitoring and logging. </p>
<ol>
&#8211; How to create an EKS cluster<br />
&#8211; Enable the EKS control plane logs and send them to CloudWatch<br />
&#8211; Send these logs from CloudWatch to OpenSearch cluster<br />
&#8211; Install Container Insights and FluentBit and send logs to CloudWatch<br />
&#8211; Install Prometheus and Grafana to monitor and visualize EKS cluster metrics<br />
&#8211; WordPress and CloudWatch log group
</ol>
<p>In order to do that, we&#8217;ll need the following CLI tools. </p>
<ol>
&#8211; eksctl<br />
&#8211; kubectl<br />
&#8211; helm<br />
&#8211; aws cli</ol>
<p>You can easily find how to install and configure these tools for various OSes. </p>
<h1>Create the EKS cluster</h1>
<p>I&#8217;ll create a managed cluster called <strong>eksWordPress </strong>in <strong>us-east-2</strong> region with <strong>two </strong><strong>t3.medium</strong> nodes. </p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksWordPress --region us-east-2 --instance-types t3.medium --nodes 2 --managed --version 1.22
</pre>
<p>If you get an error that the last supported version is 1.21, update eksctl tool. The cluster creation took about 20 mins for me. </p>
<h1>Send EKS logs to CloudWatch</h1>
<p>Once the cluster was created and up and running, enabling the EKS control plane logs is easy. There are 5 log types (API server, Audit, Authenticator, Controller Manager and Scheduler logs). The logged info is a lot, so it&#8217;s up to you what you want to log. There is no way to choose what to log, e.g. info, warning, errors. Everything is logged. To enable logging from the console, go to the EKS cluster, select it, click the <strong>Logging</strong> tab and then click on the <strong>Manage logging</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1024x278.png" alt="" width="1024" height="278" class="aligncenter size-large wp-image-9230" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1024x278.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-300x81.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-768x208.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1536x416.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-2048x555.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1920x521.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1170x317.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-585x159.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you want to do it from a CLI, type this command and specify what types of logs you want sent to CloudWatch. Specify the region and the cluster name.</p>
<pre class="brush: bash; title: ; notranslate">
aws eks update-cluster-config \
    --region &lt;REGION&gt; \
    --name &lt;CLUSTER_NAME&gt; \
    --logging &#039;{&quot;clusterLogging&quot;:&#x5B;{&quot;types&quot;:&#x5B;&quot;api&quot;,&quot;audit&quot;,&quot;authenticator&quot;,&quot;controllerManager&quot;,&quot;scheduler&quot;],&quot;enabled&quot;:true}]}&#039;
</pre>
<p>In addition, because we provisioned the cluster with the <strong>eksctl</strong> tool, we can also enable the logs using <strong>eksctl</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils update-cluster-logging --enable-types all --cluster &lt;CLUSTER_NAME&gt; --approve
</pre>
<p>Or for certain types, use&#8230; </p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils update-cluster-logging --enable-types &lt;LOG_TYPE&gt; --cluster &lt;CLUSTER_NAME&gt; --approve
</pre>
<p>You can use the same types as in the AWS CLI command above (api, audit, scheduler&#8230;).<br />
If you go to CloudWatch and then the <strong>Log groups</strong>, you&#8217;ll see the log group with the name of the EKS cluster (<em>/aws/eks/eksWordPress/cluster</em>).<br />
Make sure you change the retention from <strong>Never</strong> to some value. You probably don&#8217;t want to keep these logs indefinitely. If you look at the logs streams, you&#8217;ll see that there is a lot of info there. Most of the stuff is useless. </p>
<h1>Send the logs to OpenSearch cluster</h1>
<p>Let&#8217;s create a public OpenSearch cluster with anonymous access that only you can access. Replace the IP at the end of the statement with your IP address. It takes less than 10 mins for the OpenSearch cluster to be provisioned. </p>
<pre class="brush: bash; title: ; notranslate">
aws opensearch create-domain --domain-name oswordpress --engine-version OpenSearch_1.3 \
    --auto-tune-options DesiredState=&quot;ENABLED&quot; --cluster-config InstanceType=t3.small.search,InstanceCount=2 \
    --ebs-options EBSEnabled=true,VolumeType=gp3,VolumeSize=10,Iops=3000 \
    --access-policies &#039;{&quot;Version&quot;: &quot;2012-10-17&quot;, &quot;Statement&quot;: &#x5B;{&quot;Action&quot;: &quot;es:*&quot;, &quot;Principal&quot;:&quot;*&quot;,&quot;Effect&quot;: &quot;Allow&quot;, &quot;Condition&quot;: {&quot;IpAddress&quot;:{&quot;aws:SourceIp&quot;:&#x5B;&quot;2.18.2.19/32&quot;]}}}]}&#039;
</pre>
<p>Once the cluster is provisioned you can get the public URL with:</p>
<pre class="brush: bash; title: ; notranslate">
echo `aws es describe-elasticsearch-domain --domain-name oswordpress --output text --query &quot;DomainStatus.Endpoint&quot;`\\_dashboards
</pre>
<p>You can also get the dashboard URL from the cluster settings as well.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1024x269.png" alt="" width="1024" height="269" class="aligncenter size-large wp-image-9240" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1024x269.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-768x201.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1536x403.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-2048x537.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1920x504.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1170x307.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-585x153.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Create the following IAM policy. Save the policy below as <strong>policy-file.json</strong> but change the <strong>account_no</strong> in line 9 to match your AWS account.</p>
<pre class="brush: xml; highlight: [9]; title: ; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Action&quot;: &#x5B;
                &quot;es:*&quot;
            ],
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Resource&quot;: &quot;arn:aws:es:us-east-2:&lt;account_no&gt;:domain/oswordpress/*&quot;
        }
    ]
}
</pre>
<p>And then create the IAM policy.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-policy --policy-name polOpenSearch --policy-document file://policy-file.json
</pre>
<p>Create the role. Save the policy below as <strong>policy-trust.json</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: &#x5B;
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Service&quot;: &quot;lambda.amazonaws.com&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
</pre>
<p>And then create the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-role --role-name rolOpenSearch --assume-role-policy file://policy-trust.json
</pre>
<p>Finally, attach the policy to the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam put-role-policy --role-name rolOpenSearch --policy-name polOpenSearch --policy-document file://policy-file.json 
</pre>
<p>Go to <strong>CloudWatch</strong>, select the <strong>Log group</strong>, from the <strong>Actions</strong> button select <strong>Subscription filters</strong> and then <strong>Create Amazon OpenSearch service subscription filter</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1024x355.png" alt="" width="1024" height="355" class="aligncenter size-large wp-image-9242" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1024x355.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-300x104.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-768x266.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1536x533.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-2048x711.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1920x666.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1170x406.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-585x203.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose the OpenSearch cluster that we created, the log format is <strong>JSON</strong>, subscription filter pattern is <strong>&#8221; &#8220;</strong> for all events and type <strong>all</strong> subscription filter name or whatever you want to name this pattern. Click <strong>Start streaming</strong> button.<br />
Now, go to OpenSearch dashboard and from the hamburger menu in the upper left corner, click on <strong>Visualize</strong>. In the middle of the screen, you&#8217;ll be prompted to create an index pattern. Type <strong>cwl*</strong> and click <strong>Next step</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1024x438.png" alt="" width="1024" height="438" class="aligncenter size-large wp-image-9249" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1024x438.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-300x128.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-768x329.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1536x657.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-2048x877.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1920x822.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1170x501.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-585x250.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Select <strong>@timestamp</strong> from the drop down menu and click on <strong>Create index pattern</strong>. You&#8217;ll see the fields and the index.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1024x659.png" alt="" width="1024" height="659" class="aligncenter size-large wp-image-9251" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1024x659.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-300x193.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-768x494.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1536x989.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-2048x1319.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1920x1236.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1170x753.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-585x377.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on the <strong>Discover</strong> from the hamburger menu and you&#8217;ll see your data there. You can use queries to search your data, but that&#8217;s out of the scope of this post.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1024x400.png" alt="" width="1024" height="400" class="aligncenter size-large wp-image-9252" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1024x400.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-300x117.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-768x300.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1536x600.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-2048x800.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1920x750.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1170x457.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-585x228.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Container Insights and FluentBit</h1>
<p>Container Insights is a CloudWatch agent that we&#8217;ll install on the EKS cluster. It will collect all kinds of metrics and then FluentBit as a log forwarder will ship those logs to CloudWatch. See this link for more <a href="https://aws.amazon.com/blogs/containers/fluent-bit-integration-in-cloudwatch-container-insights-for-eks/" rel="noopener" target="_blank">info</a>.<br />
Let&#8217;s do the prerequisites work first.<br />
Get the nodes. Type the first line only, the rest is my output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes                                                                                                                                                               
NAME                                           STATUS   ROLES    AGE     VERSION
ip-192-168-56-250.us-east-2.compute.internal   Ready    &lt;none&gt;   3h39m   v1.22.12-eks-ba74326
ip-192-168-66-251.us-east-2.compute.internal   Ready    &lt;none&gt;   3h39m   v1.22.12-eks-ba74326
</pre>
<p>Get the instance ID from any of the instances, it doesn&#8217;t matter. Replace the name accordingly after <strong>Values=</strong>.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
aws ec2 describe-instances --filters &#039;Name=private-dns-name,Values=ip-192-168-56-250.us-east-2.compute.internal&#039; \
    --output text --query &#039;Reservations&#x5B;*].Instances&#x5B;*].InstanceId&#039;
i-05adaa0a823a8549a
</pre>
<p>Once you have the instance ID of any node, get the Arn of the IAM role that&#8217;s the attached to that node.</p>
<pre class="brush: bash; highlight: [1,5]; title: ; notranslate">
aws ec2 describe-instances --region us-east-2 --instance-ids i-05adaa0a823a8549a --query &#039;Reservations&#x5B;*].Instances&#x5B;*].IamInstanceProfile.Id&#039;
&#x5B;
    &#x5B;
        &quot;AIPAXFRN6SYD75I5N4BUN&quot;
    ]
]
</pre>
<p>Get the role name. Replace the ID (<strong>AIPAX</strong>&#8230;) with your value above.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws iam list-instance-profiles --query &#039;InstanceProfiles&#x5B;?InstanceProfileId==`AIPAXFRN6SYD75I5N4BUN`].Roles&#x5B;*].RoleName&#039; 
&#x5B;
    &#x5B;
        &quot;eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2&quot;
    ]
]
</pre>
<p>We want to add a policy that allows nodes to write to CloudWatch logs group. Replace the <strong>&#8211;role-name </strong>with the value that you got above.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --role-name eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2
</pre>
<pre class="brush: bash; title: ; notranslate">
oidc_id=$(aws eks describe-cluster --name eksWordPress --query &quot;cluster.identity.oidc.issuer&quot; --output text | cut -d &#039;/&#039; -f 5)
</pre>
<p>Type this line and see if there is any output. If no output, execute the command after. If yes-output, then do nothing.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam list-open-id-connect-providers | grep $oidc_id
</pre>
<p>If no output, type this.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils associate-iam-oidc-provider --cluster eksWordPress --approve
</pre>
<p>Then install CloudWatch Container Insights and FluentBit. Change the <strong>CLUSTER_NAME</strong> and <strong>REGION</strong> in lines 1 and 2.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
ClusterName=&lt;CLUSTER_NAME&gt;
RegionName=&lt;REGION&gt;
FluentBitHttpPort=&#039;2020&#039;
FluentBitReadFromHead=&#039;Off&#039;
&#x5B;&#x5B; ${FluentBitReadFromHead} = &#039;On&#039; ]] &amp;&amp; FluentBitReadFromTail=&#039;Off&#039;|| FluentBitReadFromTail=&#039;On&#039;
&#x5B;&#x5B; -z ${FluentBitHttpPort} ]] &amp;&amp; FluentBitHttpServer=&#039;Off&#039; || FluentBitHttpServer=&#039;On&#039;
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed &#039;s/{{cluster_name}}/&#039;${ClusterName}&#039;/;s/{{region_name}}/&#039;${RegionName}&#039;/;s/{{http_server_toggle}}/&quot;&#039;${FluentBitHttpServer}&#039;&quot;/;s/{{http_server_port}}/&quot;&#039;${FluentBitHttpPort}&#039;&quot;/;s/{{read_from_head}}/&quot;&#039;${FluentBitReadFromHead}&#039;&quot;/;s/{{read_from_tail}}/&quot;&#039;${FluentBitReadFromTail}&#039;&quot;/&#039; | kubectl apply -f - 
</pre>
<p>Download a config map and edit it the file called <em>cwagent-configmap.yaml</em>.</p>
<pre class="brush: bash; title: ; notranslate">
curl -O https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/cwagent/cwagent-configmap.yaml
</pre>
<p>In line 11, change the variable so it points to your cluster. In my case it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
&quot;cluster_name&quot;: &quot;{{eksWordPress}}&quot;,
</pre>
<p>Save the changes and apply the config.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f cwagent-configmap.yaml
</pre>
<p>Then deploy it as a DaemonSet.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/cwagent/cwagent-daemonset.yaml
</pre>
<p>Verify that it&#8217;s running. Type the first line only. The rest is my output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods -n amazon-cloudwatch
NAME                     READY   STATUS    RESTARTS   AGE
cloudwatch-agent-dfkzv   1/1     Running   0          18m
cloudwatch-agent-nfnf7   1/1     Running   0          18m
fluent-bit-8vd2n         1/1     Running   0          18m
fluent-bit-tvtpv         1/1     Running   0          18m
</pre>
<p>Check the logs.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl logs &lt;POD_NAME&gt;  -n amazon-cloudwatch
</pre>
<p>Or in my case&#8230;</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl logs cloudwatch-agent-dfkzv -n amazon-cloudwatch
&#x5B;2022/10/03 13:28:56] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.2] Created log stream ip-192-168-73-121.us-east-2.compute.internal.host.messages
&#x5B;2022/10/03 13:29:06] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.0] Creating log stream ip-192-168-73-121.us-east-2.compute.internal-application.var.log.containers.cloudwatch-agent-7fvxd_amazon-cloudwatch_cloudwatch-agent-5409bed9d4733a51602e4bc0cccde5e5580eb3e9282cd5cc4c1a4f2d2e28e8ea.log in log group /aws/containerinsights/eksWordPress/application
&#x5B;2022/10/03 13:29:06] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.0] Created log stream ip-192-168-73-121.us-east-2.compute.internal-application.var.log.containers.cloudwatch-agent-7fvxd_amazon-cloudwatch_cloudwatch-agent-5409bed9d4733a51602e4bc0cccde5e5580eb3e9282cd5cc4c1a4f2d2e28e8ea.log
</pre>
<p>If you go to CloudWatch now you&#8217;ll see 4 new log groups.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1024x287.png" alt="" width="1024" height="287" class="aligncenter size-large wp-image-9277" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1024x287.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-768x215.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1536x430.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-2048x573.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1920x537.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1170x327.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-585x164.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Prometheus and Grafana</h1>
<p>The Kubernetes API can also be monitored using Prometheus. We&#8217;ll install it using helm.<br />
Create a namespace first.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace prometheus
</pre>
<p>Add the Prometheus repo. </p>
<pre class="brush: bash; title: ; notranslate">
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
</pre>
<p>Deploy Prometheus.</p>
<pre class="brush: bash; title: ; notranslate">
helm upgrade -i prometheus prometheus-community/prometheus --namespace prometheus --set alertmanager.persistentVolume.storageClass=&quot;gp2&quot;,server.persistentVolume.storageClass=&quot;gp2&quot;
</pre>
<p>Check if everything is OK.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods -n prometheus
NAME                                            READY   STATUS    RESTARTS   AGE
prometheus-alertmanager-5c57cc6945-v9lcb        2/2     Running   0          4m55s
prometheus-kube-state-metrics-77ddf69b4-jsgrp   1/1     Running   0          4m55s
prometheus-node-exporter-68dk9                  1/1     Running   0          4m55s
prometheus-node-exporter-m98xk                  1/1     Running   0          4m55s
prometheus-pushgateway-ff89cc976-4sfhl          1/1     Running   0          4m55s
prometheus-server-6c99667b9b-mpw97              2/2     Running   0          4m55s
</pre>
<p>Type this command and open up a browser and go to localhost:9090.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl --namespace=prometheus port-forward deploy/prometheus-server 9090
</pre>
<p>You can CTRL-C out of the command prompt once you verify it&#8217;s OK.<br />
Let&#8217;s add the Grafana repo first.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add grafana https://grafana.github.io/helm-charts
</pre>
<p>Copy, paste and save this as <em>grafana.yaml</em>.</p>
<pre class="brush: bash; title: ; notranslate">
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server.prometheus.svc.cluster.local
      access: proxy
      isDefault: true
</pre>
<p>Create a namespace for grafana.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace grafana
</pre>
<p>Deploy using helm. Look at the <strong>adminPassword</strong> parameter. Change it to something else. In my case it&#8217;s <em>admin123!</em>.</p>
<pre class="brush: bash; title: ; notranslate">
helm install grafana grafana/grafana --namespace grafana --set persistence.storageClassName=&quot;gp2&quot; --set persistence.enabled=true --set adminPassword=&#039;admin123!&#039; --values grafana.yaml --set service.type=LoadBalancer
</pre>
<p>Check if everything is OK.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get all -n grafana
</pre>
<p>Get the URL of the classic LB that was just created.</p>
<pre class="brush: bash; title: ; notranslate">
export ELB=$(kubectl get svc -n grafana grafana -o jsonpath=&#039;{.status.loadBalancer.ingress&#x5B;0].hostname}&#039;)
echo &quot;http://$ELB&quot;
</pre>
<p>Go to that URL and you&#8217;ll see the Grafana URL.<br />
You should see the Grafana landing page. Log as admin and the password you specified when pods were deployed.<br />
In Grafana create a new dashboard and <strong>Import</strong> the dashboard with ID <strong>3119</strong>. Choose <strong>Prometheus</strong> as source.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1024x822.png" alt="" width="1024" height="822" class="aligncenter size-large wp-image-9279" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1024x822.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-300x241.png 300w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-768x616.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1536x1232.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1170x939.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-585x469.png 585w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08.png 1892w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
For pods monitoring use the same method but this time specify <strong>6417</strong> as a dashboard ID. This is how it looks like under my account.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-976x1024.png" alt="" width="976" height="1024" class="aligncenter size-large wp-image-9281" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-976x1024.png 976w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-286x300.png 286w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-768x806.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-1464x1536.png 1464w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-1170x1227.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-585x614.png 585w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09.png 1468w" sizes="(max-width: 976px) 100vw, 976px" /></a></p>
<h1>WordPress</h1>
<p>Let&#8217;s deploy WordPress.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace wordpress
</pre>
<p>Add the Bitnami helm chart.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add bitnami https://charts.bitnami.com/bitnami
</pre>
<p>Deploy WordPress in its own namespace</p>
<pre class="brush: plain; title: ; notranslate">
helm -n wordpress install understood-zebu bitnami/wordpress
</pre>
<p>Wait for 3-4 mins and do this command. This is your ELB, get the URL, something like *zdasdfa*.elb.amazonaws.com</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get svc --namespace wordpress -w understood-zebu-wordpress
</pre>
<p>The username is <strong>user</strong> and get the password with:</p>
<pre class="brush: bash; title: ; notranslate">
echo Password: $(kubectl get secret --namespace wordpress understood-zebu-wordpress -o jsonpath=&quot;{.data.wordpress-password}&quot; | base64 -d)
</pre>
<p>If you go to the ELB URL, you&#8217;ll hit WordPress main page, if you want to login, add <strong>/wp-login.php</strong> as suffix to the above URL.<br />
Go to CloudWatch and check the <strong>/aws/containerinsights/eksWordPress/application</strong> log group. You&#8217;ll see a bunch of references for WordPress. You can ship those to OpenSearch if you want and alert on errors or whatever you want to do.</p>
<h1>Delete EKS and OpenSearch cluster</h1>
<p>Detach the policy, delete the daemonset and delete the EKS cluster.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam detach-role-policy --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --role-name eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2
ClusterName=&lt;CLUSTER_NAME&gt;
RegionName=&lt;REGION&gt;
FluentBitHttpPort=&#039;2020&#039;
FluentBitReadFromHead=&#039;Off&#039;
&#x5B;&#x5B; ${FluentBitReadFromHead} = &#039;On&#039; ]] &amp;&amp; FluentBitReadFromTail=&#039;Off&#039;|| FluentBitReadFromTail=&#039;On&#039;
&#x5B;&#x5B; -z ${FluentBitHttpPort} ]] &amp;&amp; FluentBitHttpServer=&#039;Off&#039; || FluentBitHttpServer=&#039;On&#039;
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed &#039;s/{{cluster_name}}/&#039;${ClusterName}&#039;/;s/{{region_name}}/&#039;${LogRegion}&#039;/;s/{{http_server_toggle}}/&quot;&#039;${FluentBitHttpServer}&#039;&quot;/;s/{{http_server_port}}/&quot;&#039;${FluentBitHttpPort}&#039;&quot;/;s/{{read_from_head}}/&quot;&#039;${FluentBitReadFromHead}&#039;&quot;/;s/{{read_from_tail}}/&quot;&#039;${FluentBitReadFromTail}&#039;&quot;/&#039; | kubectl delete -f -
eksctl delete cluster --name eksWordPress --region=us-east-2
</pre>
<p>Delete policy.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam delete-policy --policy-arn arn:aws:iam::492943873543:policy/polOpenSearch
</pre>
<p>Delete CloudWatch log groups.</p>
<pre class="brush: bash; title: ; notranslate">
EKS_CLUSTER=eksWordPress
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/application&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/dataplane&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/host&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/performance&quot;
aws logs delete-log-group --log-group-name &quot;/aws/eks/$EKS_CLUSTER/cluster&quot;
</pre>
<p>Delete the OpenSearch cluster.</p>
<pre class="brush: bash; title: ; notranslate">
aws opensearch delete-domain --domain-name oswordpress
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: Access RDS database using PrivateLink from another account</title>
		<link>https://blog.andreev.it/2022/03/aws-access-rds-database-using-privatelink-from-another-account/</link>
					<comments>https://blog.andreev.it/2022/03/aws-access-rds-database-using-privatelink-from-another-account/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sat, 05 Mar 2022 02:03:42 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Endpoint]]></category>
		<category><![CDATA[PrivateLink]]></category>
		<category><![CDATA[RDS]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9118</guid>

					<description><![CDATA[Here is the deal. Our vendor wants to access our Aurora database. We can&#8217;t&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Here is the deal. Our vendor wants to access our Aurora database. We can&#8217;t do VPC peering because our CIDRs overlap. On top of that, we don&#8217;t want peering with 3rd party vendors and we don&#8217;t want to host our data in the vendor&#8217;s account. So, we have to use PrivateLink.<br />
In this post I&#8217;ll guide you through creating an RDS database in one account and accessing it from another account over <a href="https://aws.amazon.com/privatelink/" rel="noopener" target="_blank">PrivateLink</a>. In order to do this, we&#8217;ll need:</p>
<ul>
&#8211; Two AWS accounts (Provider and Customer). We are the provider, the vendor is the customer.<br />
&#8211; One VPC with 2 public/private subnets in each account. The vendor&#8217;s (customer) VPC config doesn&#8217;t really matter.<br />
&#8211; One EC2 instance in each account (public subnet) for testing access
</ul>
<p>We will create an endpoint service in the Provider VPC and an endpoint in the Customer VPC. The endpoint service requires a network load balancer as well. Here is a problem. When we create a NLB, the target of the NLB are IP addresses. The RDS database has DNS entries as endpoints. Technically, we can resolve the DNS URLs in IPs and use them as targets. But these IPs can change. So we have to put something in between the RDS and NLB. That&#8217;s the job of the RDS proxy. The DNS URLs of an RDS Proxy always resolve to the same IPs. Another problem is that RDS proxy can be deployed for MySQL and PostgreSQL databases only. So keep this in mind.   </p>
<h1>VPC</h1>
<p>I&#8217;ll create two VPCs in each account. The first VPC will have a 192.168.100.0/22 CIDR and the second VPC will have 192.168.200.0/22 CIDR with 4 x /24 private and public subnets. Here is the breakdown of the subnets. It&#8217;s a standard build with two NAT gateways and one Internet gateway.<br />
The first VPC is called <strong>vpc-Provider</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-01.png" alt="" width="2296" height="334" class="aligncenter size-full wp-image-9121" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-01.png 2296w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-300x44.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-1024x149.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-768x112.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-1536x223.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-2048x298.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-1920x279.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-1170x170.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-01-585x85.png 585w" sizes="(max-width: 2296px) 100vw, 2296px" /></a><br />
The second VPC is called <strong>vpc-Consumer</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-02.png" alt="" width="2056" height="354" class="aligncenter size-full wp-image-9123" /></a></p>
<h1>Security groups</h1>
<p>Create two security groups under <strong>vpc-Provider</strong> VPC, one called <strong>sgEC2</strong> and the other called <strong>sgRDS.</strong><br />
For the <strong>sgEC2</strong> group, allow SSH from some IP, e.g. your IP from work/home. The EC2 instance that we&#8217;ll create will be in a public subnet.<br />
For the <strong>sgRDS</strong> group, allow port 3306 (MySQL) from the actual subnet ID for <strong>sgEC2</strong> group and another rule to allow access to 3306 from itself. This is how the rules for <strong>sgRDS</strong> look like.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-03.png" alt="" width="2796" height="434" class="aligncenter size-full wp-image-9125" /></a><br />
Now, do the same for the <strong>vpc-Consumer account, but create only the <strong>sgEC2</strong> group.</strong><a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-03.png" alt="" width="2826" height="746" class="aligncenter size-full wp-image-9127" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-03.png 2826w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-1024x270.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-768x203.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-1536x405.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-2048x541.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-1920x507.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-1170x309.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-03-585x154.png 585w" sizes="(max-width: 2826px) 100vw, 2826px" /></a> </p>
<h1>RDS</h1>
<p>For the RDS, use pretty much default settings. In my case, I used:</p>
<ul>
&#8211; Standard create<br />
&#8211; Amazon Aurora<br />
&#8211; Amazon Aurora MySQL-Compatible Edition<br />
&#8211; Capacity type: Provisioned<br />
&#8211; Aurora (MySQL 5.7) 2.07.2<br />
&#8211; Templates: Dev/Test<br />
&#8211; Name the cluster and enter your admin credentials (remember these)<br />
&#8211; Burstable db.t3.small<br />
&#8211; Don&#8217;t create an Aurora Replica<br />
&#8211; Choose the VPC that we created<br />
&#8211; Public access: No<br />
&#8211; Security group: sgRDS<br />
&#8211; Availability Zone: No preference</ul>
<p>Then click to <strong>Create database</strong>.</p>
<h1>EC2</h1>
<p>While the database is creating, go to EC2 and create an instance that we&#8217;ll use for testing. It can be any Linux and any size. I&#8217;ve used t2.micro Amazon Linux with telnet/MySQL client installed (<em>sudo yum install telnet mysql</em>) in a public subnet. I&#8217;ve named the instance ec2Provider. Create another instance in the second account with the same specs as ec2Provider and name it ec2Customer.</p>
<h1>Test 1</h1>
<p>Go to the RDS and get the DNS record for the Aurora cluster. It&#8217;s at the bottom.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-04.png" alt="" width="2792" height="1294" class="aligncenter size-full wp-image-9132" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-04.png 2792w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-300x139.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-1024x475.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-768x356.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-1536x712.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-2048x949.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-1920x890.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-1170x542.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-04-585x271.png 585w" sizes="(max-width: 2792px) 100vw, 2792px" /></a><br />
Log to the instance in the same account (ec2Provider) and try to telnet to that port. You should be able to. CTRL-C to get out of there and log to MySQL using the client.</p>
<pre class="brush: bash; title: ; notranslate">
mysql -h database-1.cluster-cdbzhqzxu6yp.us-east-2.rds.amazonaws.com -u admin -p
</pre>
<p>Replace the hostname with yours. This the output of my connection.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-05.png" alt="" width="1298" height="814" class="aligncenter size-full wp-image-9133" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-05.png 1298w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-05-300x188.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-05-1024x642.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-05-768x482.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-05-1170x734.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-05-585x367.png 585w" sizes="(max-width: 1298px) 100vw, 1298px" /></a><br />
I&#8217;ll create a database while I am there.</p>
<pre class="brush: bash; title: ; notranslate">
create database test_db;
</pre>
<h1>AWS Secrets Manager</h1>
<p>In order to deploy an RDS proxy, we need a secret (user/password combo) stored in the AWS Secrets Manager. Use the same username and password that you used for the RDS database, which is <strong>admin </strong>and whatever password you used. Make sure you select the database that is listed at the bottom of the first screen.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-06.png" alt="" width="1079" height="814" class="aligncenter size-full wp-image-9135" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-06.png 1079w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-06-300x226.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-06-1024x773.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-06-768x579.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-06-585x441.png 585w" sizes="(max-width: 1079px) 100vw, 1079px" /></a><br />
Name the secret and proceed with the defaults.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-07.png" alt="" width="1071" height="322" class="aligncenter size-full wp-image-9136" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-07.png 1071w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-07-300x90.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-07-1024x308.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-07-768x231.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-07-585x176.png 585w" sizes="(max-width: 1071px) 100vw, 1071px" /></a></p>
<h1>RDS Proxy</h1>
<p>Go to the RDS menu and select and click on the database cluster. Scroll all the way down to the RDS Proxy section. Click on <strong>Create proxy</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-08.png" alt="" width="1557" height="360" class="aligncenter size-full wp-image-9137" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-08.png 1557w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-300x69.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-1024x237.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-768x178.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-1536x355.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-1170x271.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-08-585x135.png 585w" sizes="(max-width: 1557px) 100vw, 1557px" /></a></p>
<ul>
&#8211; Name the proxy (Proxy identifier)<br />
&#8211; Engine &#8211; MySQL<br />
&#8211; Choose the database<br />
&#8211; Uncheck the <strong>Add reader point</strong>.<br />
&#8211; Choose the secret that you created before and specify two private subnets<br />
&#8211; Leave AWS to create the IAM role.<br />
&#8211; Under <strong>Additional connectivity configuration</strong> make sure you assign the <strong>sgRDS </strong>security group and remove the default. </ul>
<p>Click on <strong>Create proxy</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-09.png" alt="" width="850" height="596" class="aligncenter size-full wp-image-9138" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-09.png 850w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-09-300x210.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-09-768x539.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-09-585x410.png 585w" sizes="(max-width: 850px) 100vw, 850px" /></a></p>
<h1>Test 2</h1>
<p>Let&#8217;s try to access our database through the proxy now. In the main menu for the RDS, click on the <strong>Proxies </strong>on the left.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-10.png" alt="" width="259" height="319" class="aligncenter size-full wp-image-9139" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-10.png 259w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-10-244x300.png 244w" sizes="(max-width: 259px) 100vw, 259px" /></a><br />
Click on the proxy and scroll down to see the proxy endpoint. Copy that value.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-11.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-11.png" alt="" width="1529" height="190" class="aligncenter size-full wp-image-9140" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-11.png 1529w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-11-300x37.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-11-1024x127.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-11-768x95.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-11-1170x145.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-11-585x73.png 585w" sizes="(max-width: 1529px) 100vw, 1529px" /></a><br />
Go to the EC2 instance in the <strong>vpc-Provider</strong>, which is in the same account as the RDS and try to access the database over proxy.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-12.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-12.png" alt="" width="943" height="394" class="aligncenter size-full wp-image-9142" /></a><br />
Great job if you made it so far!</p>
<h1>Network Load Balancer (NLB)</h1>
<p>OK, so if everything works, we need to create an NLB so we can create the endpoint service. The NLB that we&#8217;ll create requires the IP targets of the RDS proxy. You got the DNS for the RDS proxy, the one that you just tested right above. Do an <strong>nslookup </strong>on it and get the IPs.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-13.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-13.png" alt="" width="937" height="189" class="aligncenter size-full wp-image-9150" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-13.png 937w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-13-300x61.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-13-768x155.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-13-585x118.png 585w" sizes="(max-width: 937px) 100vw, 937px" /></a><br />
In my case the proxy RDS returns 192.168.103.210 and 192.168.102.160, which tells me they are in the private availability zones (AZs). Good.<br />
Create an NLB, name it and make sure it&#8217;s internal.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-14.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-14.png" alt="" width="1105" height="480" class="aligncenter size-full wp-image-9152" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-14.png 1105w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-14-300x130.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-14-1024x445.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-14-768x334.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-14-585x254.png 585w" sizes="(max-width: 1105px) 100vw, 1105px" /></a><br />
Choose the VPC and the two internal subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-15.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-15.png" alt="" width="846" height="634" class="aligncenter size-full wp-image-9153" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-15.png 846w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-15-300x225.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-15-768x576.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-15-585x438.png 585w" sizes="(max-width: 846px) 100vw, 846px" /></a><br />
When you reach the <strong>Listeners and routing</strong>, change the port to 3306 and click on <strong>Create target group</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-16.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-16.png" alt="" width="1117" height="339" class="aligncenter size-full wp-image-9154" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-16.png 1117w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-16-300x91.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-16-1024x311.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-16-768x233.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-16-585x178.png 585w" sizes="(max-width: 1117px) 100vw, 1117px" /></a><br />
Choose <strong>IP addresses</strong>, name the <strong>Target group name</strong> and change the <strong>port </strong>to 3306. The correct VPC should be selected.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-17.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-17.png" alt="" width="874" height="803" class="aligncenter size-full wp-image-9155" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-17.png 874w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-17-300x276.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-17-768x706.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-17-585x537.png 585w" sizes="(max-width: 874px) 100vw, 874px" /></a><br />
Leave the health checks, they are automatically populated for the right port and click <strong>Next</strong>.<br />
On the next screen below, enter the IPs that you got from the nslookup for the RDS proxy. In my case it looks like this. Make sure the port is 3306 and click on <strong>Include as pending below</strong> and finally click on <strong>Create target group</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-18.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-18.png" alt="" width="1448" height="732" class="aligncenter size-full wp-image-9157" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-18.png 1448w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-18-300x152.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-18-1024x518.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-18-768x388.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-18-1170x591.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-18-585x296.png 585w" sizes="(max-width: 1448px) 100vw, 1448px" /></a><br />
Go back to the original browser tab where you create the NLB and click the refresh button. You&#8217;ll be able to select the target group now.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-19.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-19.png" alt="" width="1047" height="248" class="aligncenter size-full wp-image-9159" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-19.png 1047w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-19-300x71.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-19-1024x243.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-19-768x182.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-19-585x139.png 585w" sizes="(max-width: 1047px) 100vw, 1047px" /></a><br />
Click on <strong>Create load balancer</strong> at the bottom. It takes a couple of minutes to provision the NLB. Once done, click on the <strong>Listeners </strong>tab and then the <strong>target group</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-20.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-20.png" alt="" width="889" height="356" class="aligncenter size-full wp-image-9160" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-20.png 889w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-20-300x120.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-20-768x308.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-20-585x234.png 585w" sizes="(max-width: 889px) 100vw, 889px" /></a><br />
It will take another couple of minutes, but you should see that the targets are unhealthy. Why is that? Because our RDS proxy can get requests from the EC2 instances only. Thе NLB can&#8217;t access the RDS proxy. Per AWS, we have to allow the CIDRs of the NLB to access the RDS proxy. That&#8217;s easy. We created an internal NLB with 192.168.102.0/24 and 192.168.103.0/24 subnets, so we have to modify the <strong>sgRDS </strong>security group. Do the same. This is how my security group <strong>sgRDS </strong>looks like now.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-21.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-21.png" alt="" width="1444" height="280" class="aligncenter size-full wp-image-9161" /></a><br />
And if you check the target group, you should see both RDS proxy instance as healthy.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-22.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-22.png" alt="" width="1488" height="749" class="aligncenter size-full wp-image-9171" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-22.png 1488w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-22-300x151.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-22-1024x515.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-22-768x387.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-22-1170x589.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-22-585x294.png 585w" sizes="(max-width: 1488px) 100vw, 1488px" /></a></p>
<h1>Test 3</h1>
<p>Let&#8217;s check the access on the NLB. Get the DNS name. In my case it&#8217;s rds-nlb-40d1b9bfc867b2fb.elb.us-east-2.amazonaws.com.<br />
Go back to the EC2 instance in the same account and try to access the database.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-24.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-24.png" alt="" width="903" height="405" class="aligncenter size-full wp-image-9173" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-24.png 903w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-24-300x135.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-24-768x344.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-24-585x262.png 585w" sizes="(max-width: 903px) 100vw, 903px" /></a><br />
Yep, we are doing great. Let&#8217;s recap. We can&#8217;t access the RDS directly from another account because &#8211; well, it&#8217;s another account that we don&#8217;t trust and our RDS DB is in a private subnet. So, we have to create an endpoint service that requires a network load balancer. But, the NLB requires IP targets and our RDS endpoints are DNS names. Which means that the DNS names can change its IPs, so we used an RDS proxy DNS names converted to IPs which AWS claims that they won&#8217;t change. </p>
<h1>Endpoint service</h1>
<p>The Provider (we) creates an Endpoint service, the Customer (the vendor) creates the Endpoint. Simple as that. It doesn&#8217;t have to be a vendor. Maybe the proper term is the Consumer. So, we (the provider) expose some service as Endpoint Service and someone else tries to access it.<br />
An Endpoint service requires a network load balancer. We did that. So let&#8217;s create the endpoint service. It&#8217;s under the VPC menu.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-25.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-25.png" alt="" width="175" height="395" class="aligncenter size-full wp-image-9175" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-25.png 175w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-25-133x300.png 133w" sizes="(max-width: 175px) 100vw, 175px" /></a><br />
Click on <strong>Endpoint Services</strong> and then <strong>Create endpoint service</strong>.<br />
Name the endpoint service and check the network load balancer. Make sure that the <strong>Acceptance required</strong> is checked.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-26.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-26.png" alt="" width="855" height="666" class="aligncenter size-full wp-image-9176" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-26.png 855w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-26-300x234.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-26-768x598.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-26-585x456.png 585w" sizes="(max-width: 855px) 100vw, 855px" /></a><br />
Once created (in a second), get the service name. Copy that URL highlighted.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-27.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-27.png" alt="" width="1308" height="671" class="aligncenter size-full wp-image-9179" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-27.png 1308w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-27-300x154.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-27-1024x525.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-27-768x394.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-27-1170x600.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-27-585x300.png 585w" sizes="(max-width: 1308px) 100vw, 1308px" /></a><br />
Select the endpoint service and from the Actions menu, choose <strong>Allow principals</strong>.  Add * for ARN which means everyone. Now this is really bad. For the sake of simplicity, I&#8217;ve added * (everyone) but you want to add an ARN role from the other account.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-28.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-28.png" alt="" width="917" height="608" class="aligncenter size-full wp-image-9181" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-28.png 917w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-28-300x199.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-28-768x509.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-28-780x516.png 780w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-28-585x388.png 585w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-28-263x175.png 263w" sizes="(max-width: 917px) 100vw, 917px" /></a></p>
<h1>Endpoint</h1>
<p>The endpoint is the Consumer, the vendor or some other VPC. Go to that account. We are no longer working on the original account, log to the second account and go to the <strong>Endpoint </strong>under VPC menu. Click on <strong>Create endpoint</strong>. Enter the name for endpoint, click on <strong>Other endpoint services</strong> and paste the endpoint that you got in the previous step. Click on <strong>Verify service</strong> and you should see that it&#8217;s OK.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-29.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-29.png" alt="" width="842" height="668" class="aligncenter size-full wp-image-9183" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-29.png 842w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-29-300x238.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-29-768x609.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-29-585x464.png 585w" sizes="(max-width: 842px) 100vw, 842px" /></a><br />
Scroll down and choose the VPC where you want to create the endpoint. In our case, we want to use the EC2 instance that&#8217;s in the public subnet. Add the two public subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-31.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-31.png" alt="" width="891" height="745" class="aligncenter size-full wp-image-9188" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-31.png 891w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-31-300x251.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-31-768x642.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-31-585x489.png 585w" sizes="(max-width: 891px) 100vw, 891px" /></a><br />
And for the security group, add the EC2 security group.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-32.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-32.png" alt="" width="909" height="285" class="aligncenter size-full wp-image-9189" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-32.png 909w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-32-300x94.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-32-768x241.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-32-585x183.png 585w" sizes="(max-width: 909px) 100vw, 909px" /></a><br />
NOTE: If you go to the <strong>Interfaces </strong>section under EC2, you&#8217;ll see three NICs. One is for the EC2 instance that we&#8217;ve created, the other two are the endpoints.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-30.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-30.png" alt="" width="1414" height="293" class="aligncenter size-full wp-image-9186" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-30.png 1414w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-30-300x62.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-30-1024x212.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-30-768x159.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-30-1170x242.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-30-585x121.png 585w" sizes="(max-width: 1414px) 100vw, 1414px" /></a><br />
Anyway, once you create the endpoint, you&#8217;ll see this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-33.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-33.png" alt="" width="719" height="294" class="aligncenter size-full wp-image-9192" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-33.png 719w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-33-300x123.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-33-585x239.png 585w" sizes="(max-width: 719px) 100vw, 719px" /></a><br />
Go to the other account (Provider) and under the endpoint services (VPC menu, Endpoint connections tab), you&#8217;ll see a request for the connection. That&#8217;s what <strong>Accepance required</strong> means when we created the <strong>Endpoint Service</strong>. Accept the connection. It takes about 30 seconds to complete the acceptance. </p>
<h1>Test 4</h1>
<p>Go to the Consumer account and click on <strong>Endpoint </strong>and then select the endpoint. Under the <strong>Details </strong>tab you&#8217;ll see three DNS entries.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-34.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-34.png" alt="" width="691" height="883" class="aligncenter size-full wp-image-9194" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-34.png 691w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-34-235x300.png 235w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-34-585x748.png 585w" sizes="(max-width: 691px) 100vw, 691px" /></a><br />
Copy one of them and log to the EC2 instance in that account and try to access the RDS. </p>
<pre class="brush: bash; title: ; notranslate">
mysql -h vpce-08e3fbbe49e5f241b-q4e8rrlt.vpce-svc-01cc20f911a4789ff.us-east-2.vpce.amazonaws.com -u admin -p
</pre>
<p>You won&#8217;t be able to do so. Finally, edit the security group sgEC2 that we assigned to the endpoint and add a reference to itself for port 3306. It looks like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-35.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-35.png" alt="" width="1677" height="762" class="aligncenter size-full wp-image-9195" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-35.png 1677w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-300x136.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-1024x465.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-768x349.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-1536x698.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-1170x532.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-35-585x266.png 585w" sizes="(max-width: 1677px) 100vw, 1677px" /></a><br />
And if you try to access the RDS now, you&#8217;ll be able to do so.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-36.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-36.png" alt="" width="888" height="283" class="aligncenter size-full wp-image-9196" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-36.png 888w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-36-300x96.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-36-768x245.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-36-585x186.png 585w" sizes="(max-width: 888px) 100vw, 888px" /></a></p>
<h1>Endpoint service</h1>
<p>You can also go the the Provider account and delete the principal under <strong>Allowed principals</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/03/P159-37.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/03/P159-37.png" alt="" width="1836" height="822" class="aligncenter size-full wp-image-9198" srcset="https://blog.andreev.it/wp-content/uploads/2022/03/P159-37.png 1836w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-300x134.png 300w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-1024x458.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-768x344.png 768w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-1536x688.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-1170x524.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/03/P159-37-585x262.png 585w" sizes="(max-width: 1836px) 100vw, 1836px" /></a><br />
It&#8217;s not needed anymore.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2022/03/aws-access-rds-database-using-privatelink-from-another-account/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
	</channel>
</rss>
