<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohamed Radwan</title>
    <description>The latest articles on DEV Community by Mohamed Radwan (@maradwan).</description>
    <link>https://dev.to/maradwan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F720938%2F2fd0db10-0c82-4989-90b1-7d2dbdcb3c67.jpeg</url>
      <title>DEV Community: Mohamed Radwan</title>
      <link>https://dev.to/maradwan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maradwan"/>
    <language>en</language>
    <item>
      <title>How "Vibe Coding" Accidentally Turned My EC2 Instance Into a Cryptominer</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Wed, 01 Jul 2026 11:37:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/how-vibe-coding-accidentally-turned-my-ec2-instance-into-a-cryptominer-52n2</link>
      <guid>https://dev.to/aws-builders/how-vibe-coding-accidentally-turned-my-ec2-instance-into-a-cryptominer-52n2</guid>
      <description>&lt;p&gt;A few days ago I got an email I did not expect: an abuse report from AWS Trust &amp;amp; Safety, saying my EC2 instance in my personal AWS account had been caught scanning other hosts on the internet.&lt;br&gt;
My first thought was the obvious one someone hacked my box. &lt;br&gt;
What I actually found was more interesting (and honestly, kind of embarrassing): a single line in a package.json file was all it took.&lt;/p&gt;

&lt;p&gt;Here's the full story of how it happened, how I tracked it down, and what actually fixed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The abuse report&lt;/strong&gt;&lt;br&gt;
The email included a forwarded report from a third party network. They'd logged multiple completed TCP handshakes from my instance's public IP, hitting a handful of destination IPs on ports like 9200 (commonly Elasticsearch), 443, and 80.&lt;br&gt;
The "completed handshake" detail matters — it rules out IP spoofing. Both sides have to respond for a handshake to finish, so this wasn't some rando forging my IP. My instance really was reaching out and probing other hosts.&lt;br&gt;
Time to figure out why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First theory: someone broke in&lt;/strong&gt;&lt;br&gt;
Outbound scanning traffic screams "compromised box," so I started where anyone would: how did they get in?&lt;/p&gt;

&lt;p&gt;SSH brute force?&lt;br&gt;
Nope. SSH was key only, and every entry in auth.log was Accepted publickey from IPs I recognized. No password attempts, no unfamiliar sources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accepted publickey for ubuntu from &amp;lt;trusted-ip&amp;gt; port 57322 ssh2: RSA SHA256:...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;An exposed service?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything else was completely unreachable from the internet.&lt;br&gt;&lt;br&gt;
The security group sat in front of all of it. Dead end.&lt;br&gt;
So: no brute force, no exposed service. Whatever this was, it didn't come in through the front door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding the malware&lt;/strong&gt;&lt;br&gt;
A quick find / -mtime -2 (files modified in the last 2 days) turned up something ugly, sitting right in my frontend app's directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scanner_linux
xmrig.tar.gz
scanner_deployed.log
exploited.log
failed.log
monitor.log
data.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;xmrig is a legitimate (but heavily abused) Monero mining tool. scanner_linux was a binary I'd never seen before — and ps aux confirmed it was actively running, as root, chewing through CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;root  285679  27.7% CPU  ./scanner_linux -t 1000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it was. That process was almost certainly what generated the traffic AWS flagged.&lt;/p&gt;

&lt;p&gt;Both files were owned by root, written within about a minute of each other, at a time that didn't line up with any of my own SSH sessions. Whatever dropped them had root level filesystem access  but never touched SSH to get it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Following the trail outward&lt;/strong&gt;&lt;br&gt;
If nothing got in from the outside, something on the inside must have reached out. I checked the security group's outbound rule:&lt;br&gt;
&lt;code&gt;Outbound: All traffic → 0.0.0.0/0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual root cause&lt;/strong&gt;&lt;br&gt;
Digging through the frontend app's &lt;strong&gt;package.json&lt;/strong&gt;, one line jumped out immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"zod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"child_process"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws-amplify"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;child_process&lt;/strong&gt; is not a real npm package. It's a built-in Node.js core module, no install required, ever. &lt;br&gt;
There is zero legitimate reason for it to show up as an external dependency.&lt;br&gt;
But somebody had published a package under that exact name on the public npm registry. This is a known attack pattern: squat on a name that looks like a trusted standard-library module, and wait for someone (a developer copy-pasting a snippet, or these days, an AI coding assistant hallucinating a dependency) to install it by mistake.&lt;/p&gt;

&lt;p&gt;These packages almost always ship a postinstall script, which npm runs automatically the second npm install finishes — silently, with whatever permissions the install process has. In my case, that was root, inside a container with a bind-mounted host directory. Every rebuild of that container had been quietly re-running it.&lt;/p&gt;

&lt;p&gt;The fix: remove the child_process line from package.json, wipe node_modules and the lockfile, and rebuild from scratch with docker compose up -d --build — the --build flag matters, since a plain restart just resumes the already-infected image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'm taking away from this&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A locked-down security group only stops external attackers. It does nothing once malicious code is already executing locally with your own privileges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wide-open outbound rules are underrated as a risk. 0.0.0.0/0 outbound is a common default, but it's exactly what let a tiny postinstall script fetch a cryptominer. Restricting egress would've stopped this cold even after the bad package installed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any dependency with the same name as a Node built-in is an instant red flag. child_process, fs, http, etc. should never appear in your dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stop pinning things to "latest". Half my dependency list was unpinned, which makes it way harder to spot when something new and unwanted sneaks in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;npm audit and a manual skim of package.json should be routine, not just a post incident activity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>devops</category>
      <category>ai</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Sat, 19 Jul 2025 20:39:04 +0000</pubDate>
      <link>https://dev.to/maradwan/-45og</link>
      <guid>https://dev.to/maradwan/-45og</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40" class="crayons-story__hidden-navigation-link"&gt;Enriching Keycloak with LinkedIn VanityName, Headline &amp;amp; Profile Picture via Custom SPI&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/maradwan" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F720938%2F2fd0db10-0c82-4989-90b1-7d2dbdcb3c67.jpeg" alt="maradwan profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/maradwan" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mohamed Radwan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mohamed Radwan
                
              
              &lt;div id="story-author-preview-content-2611866" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/maradwan" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F720938%2F2fd0db10-0c82-4989-90b1-7d2dbdcb3c67.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mohamed Radwan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 21 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40" id="article-link-2611866"&gt;
          Enriching Keycloak with LinkedIn VanityName, Headline &amp;amp; Profile Picture via Custom SPI
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/keycloak"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;keycloak&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>keycloak</category>
      <category>opensource</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Sun, 13 Jul 2025 12:39:14 +0000</pubDate>
      <link>https://dev.to/maradwan/-1mpd</link>
      <guid>https://dev.to/maradwan/-1mpd</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7" class="crayons-story__hidden-navigation-link"&gt;Extract Invoice Data Automatically Using LangChain&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/maradwan" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F720938%2F2fd0db10-0c82-4989-90b1-7d2dbdcb3c67.jpeg" alt="maradwan profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/maradwan" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Mohamed Radwan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Mohamed Radwan
                
              
              &lt;div id="story-author-preview-content-2680895" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/maradwan" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F720938%2F2fd0db10-0c82-4989-90b1-7d2dbdcb3c67.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Mohamed Radwan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 13 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7" id="article-link-2680895"&gt;
          Extract Invoice Data Automatically Using LangChain
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/langchain"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;langchain&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cognito"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cognito&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>langchain</category>
    </item>
    <item>
      <title>Extract Invoice Data Automatically Using LangChain</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Sun, 13 Jul 2025 12:01:54 +0000</pubDate>
      <link>https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7</link>
      <guid>https://dev.to/aws-builders/extract-invoice-data-automatically-using-langchain-ga7</guid>
      <description>&lt;p&gt;In this article, I’m sharing an app I built to automate invoice processing using image recognition and language models. The goal is simple: take scanned or photographed invoices (in JPG, PNG, or PDF format) and extract structured data in JSON.&lt;/p&gt;

&lt;p&gt;Under the hood, the system uses OpenAI’s GPT-4o (GPT-4 Vision) model via LangChain, and it’s wrapped in a lightweight FastAPI backend built with Python. The app can batch process files, run locally or in a container, and outputs clean JSON ready for downstream systems like accounting tools or CRMs.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Tech Stack &amp;amp; Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The app is structured with a modern, API-first architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;: Python with FastAPI, using LangChain + GPT-4o for invoice processing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: AWS Cognito for secure, scalable user auth&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;: MongoDB for storing processed invoice data and metadata&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;: A Next.js app handles the UI and connects to the backend via API&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/maradwan/invoice-backend" rel="noopener noreferrer"&gt;Backend Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The authentication layer with AWS Cognito makes it easy to manage user sign-ups, login access. Invoice data and any synced product info are stored in MongoDB, which works well with JSON-like structures. &lt;br&gt;
On the frontend, Next.js provides a fast and reactive UI that lets users upload invoice images, view extracted data, and manage syncing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sync Invoice Items with Product Barcodes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the app extracts line items from the invoice such as product names, you can take it a step further by syncing these items with your internal product database. This allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Match items by name&lt;/li&gt;
&lt;li&gt;Automatically assign or update barcodes&lt;/li&gt;
&lt;li&gt;Link products to existing inventory systems&lt;/li&gt;
&lt;li&gt;Detect mismatches or missing items&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>langchain</category>
      <category>aws</category>
      <category>cognito</category>
    </item>
    <item>
      <title>Enriching Keycloak with LinkedIn VanityName, Headline &amp; Profile Picture via Custom SPI</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Sat, 21 Jun 2025 10:57:36 +0000</pubDate>
      <link>https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40</link>
      <guid>https://dev.to/aws-builders/enriching-keycloak-with-linkedin-vanityname-headline-profile-picture-via-custom-spi-g40</guid>
      <description>&lt;p&gt;When integrating LinkedIn as an Identity Provider in Keycloak, you may quickly discover a limitation: by default, LinkedIn only provides basic user information such as email, first name, and last name.&lt;/p&gt;

&lt;p&gt;However, in many real-world applications, you may also want to retrieve and store additional profile details, including:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vanity name (public LinkedIn profile ID)

High-resolution profile picture

Professional headline (user's job title or tagline)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To achieve this, you'll need to create a custom Identity Provider Mapper SPI (Service Provider Interface) in Keycloak. This extension allows you to fetch additional data from LinkedIn's API and map it into the Keycloak user model during the authentication process.&lt;/p&gt;

&lt;p&gt;Step 1: Set Up Your LinkedIn App Permissions&lt;/p&gt;

&lt;p&gt;Before you begin implementing the custom SPI, make sure your LinkedIn developer app has the correct permissions. You'll need to request access to the following&lt;/p&gt;

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

&lt;p&gt;These scopes allow your Keycloak instance to retrieve detailed user information such as their profile picture and professional headline.&lt;br&gt;
 Configuring LinkedIn as an Identity Provider in Keycloak (Tested on Keycloak 26.2.5)&lt;/p&gt;

&lt;p&gt;After you've created a LinkedIn App and obtained the required credentials, follow these steps to configure LinkedIn in your example Keycloak realm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to your Keycloak admin console.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to:&lt;br&gt;
Identity Providers → Select LinkedIn.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fill in the following fields:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;        Client ID: (from your LinkedIn app)&lt;/li&gt;
&lt;li&gt;        Client Secret: (from your LinkedIn app)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;In the Scopes field, add the following permissions:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;openid profile email r_basicprofile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These scopes are necessary to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Authenticate the user (openid)&lt;/li&gt;
&lt;li&gt;    Retrieve profile information (profile, r_basicprofile)&lt;/li&gt;
&lt;li&gt;    Fetch the user’s verified email address (email)&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Enable the following options:&lt;/li&gt;
&lt;li&gt;Store Tokens (This is required for the SPI to extract the LinkedIn access token and make additional API calls.)&lt;/li&gt;
&lt;li&gt;Trust Email (Assumes the email provided by LinkedIn is verified.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click Save.&lt;/p&gt;

&lt;p&gt;To use this LinkedIn SPI extension in your Keycloak setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Download the JAR file&lt;br&gt;
Either build it yourself using Maven, or download a prebuilt version from the &lt;a href="https://github.com/maradwan/keycloak-linkedin-profile-mapper/releases" rel="noopener noreferrer"&gt;GitHub Releases page&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Copy the JAR to Keycloak's provider directory&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp linkedin-profile-mapper-1.0.0.jar /opt/keycloak/providers/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Rebuild Keycloak to recognize the new provider
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt/keycloak/bin/kc.sh build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Restart Keycloak
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /opt/keycloak/bin/kc.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Source Code &amp;amp; Releases&lt;br&gt;
You can find the full source code and latest releases at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/maradwan/keycloak-linkedin-profile-mapper

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you need to add Identity Provider Mapper &lt;/p&gt;

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

&lt;p&gt;Then you need to add attributes for vanityName, profilePicture, and headline in the User profile that is in the Realm settings of your example realm &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Create an example-client in Keycloak
&lt;/h2&gt;

&lt;p&gt;In your Keycloak Admin Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to your realm (e.g., example), then navigate to Clients → click Create client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter example-client as the Client ID, choose OpenID Connect, and click Next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set Root URL to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, and Valid Redirect URI to &lt;a href="http://localhost:3000/callback" rel="noopener noreferrer"&gt;http://localhost:3000/callback&lt;/a&gt;, then click Save.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the Credentials tab → copy the Client Secret.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under Settings, enable Standard Flow, optionally enable Direct Access Grants.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use these details in your app:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Test the Integration (Optional FastAPI Example)
&lt;/h2&gt;

&lt;p&gt;You can test the LinkedIn login flow and see the enriched tokens by using a simple FastAPI application:&lt;/p&gt;

&lt;p&gt;Install the required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install fastapi uvicorn requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app.py
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
import requests

app = FastAPI()

# === CONFIG ===
KEYCLOAK_BASE_URL = "https://idp.example.com"
REALM = "example"
CLIENT_ID = "example-client"
CLIENT_SECRET = "XXXXXXXXX"  # Only needed for confidential clients
REDIRECT_URI = "http://localhost:3000/callback"  # Your app's redirect URI

# === ROUTES ===

@app.get("/login")
def login():
    return RedirectResponse(
        f"{KEYCLOAK_BASE_URL}/realms/{REALM}/protocol/openid-connect/auth"
        f"?client_id={CLIENT_ID}"
        f"&amp;amp;redirect_uri={REDIRECT_URI}"
        f"&amp;amp;response_type=code"
        f"&amp;amp;scope=openid"
    )

@app.get("/callback")
def callback(request: Request):
    code = request.query_params.get("code")
    if not code:
        return {"error": "No authorization code provided"}

    # Exchange code with Keycloak (not LinkedIn)
    token_response = requests.post(
        f"{KEYCLOAK_BASE_URL}/realms/{REALM}/protocol/openid-connect/token",
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": REDIRECT_URI,
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )

    if token_response.status_code != 200:
        return {"error": "Token exchange failed", "details": token_response.json()}

    token_data = token_response.json()

    # Decode the token to inspect (optional)
    id_token = token_data.get("id_token")
    access_token = token_data.get("access_token")
    return {
        "access_token": access_token,
        "id_token": id_token,
        "message": "Use this token to call secured APIs or decode for LinkedIn data",
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the App&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uvicorn app:app --reload --port 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Sources:&lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/linkedin/marketing/quick-start?view=li-lms-2025-03#step-1-apply-for-api-access" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/linkedin/marketing/quick-start?view=li-lms-2025-03#step-1-apply-for-api-access&lt;/a&gt;&lt;/p&gt;

</description>
      <category>keycloak</category>
      <category>opensource</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Mon, 13 Jan 2025 09:23:36 +0000</pubDate>
      <link>https://dev.to/maradwan/-131</link>
      <guid>https://dev.to/maradwan/-131</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10" class="crayons-story__hidden-navigation-link"&gt;How I automated Certificate expiration alerts with AWS&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/teti_most" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1126677%2F13a7aa1d-6abb-48cb-92f0-790836504f90.PNG" alt="teti_most profile" class="crayons-avatar__image" width="800" height="794"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/teti_most" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Tetiana Mostova
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Tetiana Mostova
                
              
              &lt;div id="story-author-preview-content-2202544" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/teti_most" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1126677%2F13a7aa1d-6abb-48cb-92f0-790836504f90.PNG" class="crayons-avatar__image" alt="" width="800" height="794"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Tetiana Mostova&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 12 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10" id="article-link-2202544"&gt;
          How I automated Certificate expiration alerts with AWS
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aws"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aws&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;12&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/how-i-automated-certificate-expiration-alerts-with-aws-1g10#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Wed, 25 Dec 2024 12:34:15 +0000</pubDate>
      <link>https://dev.to/maradwan/-54pg</link>
      <guid>https://dev.to/maradwan/-54pg</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/installing-argocd-and-securing-access-using-amazon-cognito-3gnn" class="crayons-story__hidden-navigation-link"&gt;Installing ArgoCD and Securing Access Using Amazon Cognito&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/ravindras" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1197801%2Fdbe218cb-52e9-4537-9087-e8ff2e45956c.jpeg" alt="ravindras profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ravindras" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ravindra Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ravindra Singh
                
              
              &lt;div id="story-author-preview-content-2011686" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ravindras" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1197801%2Fdbe218cb-52e9-4537-9087-e8ff2e45956c.jpeg" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ravindra Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/installing-argocd-and-securing-access-using-amazon-cognito-3gnn" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 27 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/installing-argocd-and-securing-access-using-amazon-cognito-3gnn" id="article-link-2011686"&gt;
          Installing ArgoCD and Securing Access Using Amazon Cognito
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kubernetes"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kubernetes&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/eks"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;eks&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cognito"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cognito&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/installing-argocd-and-securing-access-using-amazon-cognito-3gnn" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;17&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/installing-argocd-and-securing-access-using-amazon-cognito-3gnn#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              4&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>aws</category>
      <category>community</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Optimize AWS Cloud Costs</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Mon, 11 Nov 2024 18:01:21 +0000</pubDate>
      <link>https://dev.to/maradwan/optimize-aws-cloud-costs-2g4m</link>
      <guid>https://dev.to/maradwan/optimize-aws-cloud-costs-2g4m</guid>
      <description>&lt;p&gt;Optimize AWS cloud costs by implementing the following strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VPC Optimization&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manage Data Transfer Costs&lt;/strong&gt;: NAT gateways incur costs; in most cases, two subnets with 99.9% availability are sufficient. For 99.99% availability, use three subnets with three NAT gateways. Two subnets are generally sufficient for most scenarios.&lt;/li&gt;
&lt;li&gt;Remove IP v4 addresses that are not needed or associated with Network Interface Card (NIC).&lt;/li&gt;
&lt;li&gt;Use VPC endpoints to privately connect to supported AWS services and VPC endpoint services, rather than relying on NAT gateways, to reduce costs. Additionally, AWS PrivateLink is often required for other purposes, such as compliance, irrespective of cost considerations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EKS/EC2/Auto Scaling Group (ASG) Optimization&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implement Instance Auto scaling&lt;/strong&gt;: Configure autoscaling with Spot instances for worker nodes by using &lt;a href="https://karpenter.sh/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt;  to adjust resources based on demand dynamically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Reserved Instances and Savings Plans&lt;/strong&gt;: Optimize for a single node type, such as &lt;code&gt;m5a.large&lt;/code&gt; or &lt;code&gt;m5a.xlarge&lt;/code&gt; , for most applications to gain cost efficiencies through reserved capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rightsize Resources&lt;/strong&gt;: Regularly reassess applications and nodes to evaluate utilization and rightsize resources for optimized performance and cost.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;scheduled actions&lt;/strong&gt; in the Auto Scaling Group ASG for test and development environments to automatically stop instances at specified times when compute nodes are not in use and start them when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU Type&lt;/strong&gt;: ARM-based CPUs (such as AWS Graviton) are more cost-effective but may have compatibility issues with certain applications. Developers should ensure application compatibility to support multiple CPU types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrades&lt;/strong&gt;: Cloud providers may impose fees for clusters that are not upgraded to a supported version. For EKS, using outdated Kubernetes Cluster requires Extended Support, resulting in additional expenses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EBS Optimization&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that EBS volumes are configured to use the GP3 storage type, which is 20% more cost-effective than GP2.&lt;/li&gt;
&lt;li&gt;Delete unused EBS volumes and snapshots that are no longer in use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;S3 Optimization&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimize Storage Costs for Object Storage&lt;/strong&gt;: Configure lifecycle policies, especially when using versioning, and choose the appropriate storage class (e.g., Standard or Infrequent Access). Consider S3 Intelligent Tiering for automated cost-efficient storage management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Database Optimization&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For RDS, consider rightsizing, using Graviton-based instances, and performing mass engine version upgrades if applications support the new version.&lt;/li&gt;
&lt;li&gt;Switching to Amazon Aurora I/O-Optimized increases instance costs by approximately 25% to include I/O operations, making it an ideal choice for high I/O workloads. This option offers improved cost predictability and can potentially reduce total costs when I/O demand is significant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrades&lt;/strong&gt;: Cloud providers may impose fees for clusters that are not upgraded to a supported version. For RDS, using outdated database engines may require Extended Support, resulting in additional expenses.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>finops</category>
    </item>
    <item>
      <title>Take IT Shipping with Serverless Technology</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Thu, 21 Mar 2024 12:35:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/take-it-shipping-with-serverless-technology-ehi</link>
      <guid>https://dev.to/aws-builders/take-it-shipping-with-serverless-technology-ehi</guid>
      <description>&lt;p&gt;In this article, I'll take you through the journey of creating a mobile application utilizing serverless architecture. &lt;/p&gt;

&lt;p&gt;The app idea is a platform that facilitates and supports peer-to-peer shipping services, connecting requesters and travelers.&lt;/p&gt;

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

&lt;p&gt;FrontEnd &lt;a href="https://github.com/maradwan/takeit-app" rel="noopener noreferrer"&gt;Flutter Code&lt;/a&gt;&lt;br&gt;
Backend &lt;a href="https://github.com/maradwan/takeit-backend" rel="noopener noreferrer"&gt;Lambda Code&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The App Architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Framework (Flutter)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Flutter serves as the frontend framework for developing the mobile application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flutter allows for cross-platform development, enabling the app to run on both iOS and Android devices from a single codebase.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend Services (Lambda)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;The backend service is built using Lambda, a serverless computing platform.&lt;br&gt;
Lambda functions are deployed in response to events triggered by user actions, ensuring scalability and cost-efficiency.&lt;br&gt;
"The Take IT" app utilizes Python Flask within the Lambda functions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication (Cognito):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cognito, a serverless authentication service, manages the sign-up/sign-in process in the Take IT app. Additionally, it seamlessly integrates with identity providers such as Google or Facebook. Authentication is typically managed using JWT (JSON Web Tokens) for secure and efficient user authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API Gateway:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Integrating AWS API Gateway with Cognito offers enhanced security and scalability for the Take IT app. &lt;br&gt;
API Gateway ensures only authenticated users can access backend services. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database (DynamoDB)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;DynamoDB serves as the serverless database utilized to store all data in the Take IT app. It is leveraged to manage user preferences, trip details, contacts, as well as the status of sent and received requests (including accepted, pending, and declined requests).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding a Trip&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;The function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def add_trip(item):
    return query_table.put_item(Item=item)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is an example of how a trip is saved into DynamoDB in JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "username": "4fb1dce7-6a50-41a8-8d7d",
 "created": "2024-10-04-19-34-18-736061",
 "acceptfrom": "2024-10-05",
 "acceptto": "2024-10-30",
 "allowed": {
  "Clothes": {
   "cost": "3.0",
   "kg": "10.0"
  },
  "Electronics": {
   "cost": "20.0",
   "kg": "5.0"
  }
 },
 "currency": "EUR",
 "fromcity": "Berlin-Germany",
 "fromto": "Berlin-Germany_Cairo-Egypt",
 "tocity": "Cairo-Egypt",
 "trdate": "2024-10-31",
 "tstamp": 1732961762
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By utilizing the TTL (Time to Live) feature in DynamoDB, you can automatically delete records after a specified time period. For instance, in the trip record, there is an attribute called "tstamp" that determines the deletion time of the record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find available trips&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The attributes in the trip record, such as "fromCity," "toCity," or "fromTo," are utilized for search functionality when users seek trips. I employ a global secondary index in DynamoDB to retrieve trips based on the originating city, destination city, or the combination of both.&lt;/p&gt;

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

&lt;p&gt;The function looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Index: This refers to the name of the global secondary index.&lt;/li&gt;
&lt;li&gt;Key: This represents the DynamoDB attribute used for indexing, typically referring to "fromCity" or "toCity."&lt;/li&gt;
&lt;li&gt;City: Denotes the name of the city being referenced.&lt;/li&gt;
&lt;li&gt;Limit: Specifies the maximum number of records to retrieve.&lt;/li&gt;
&lt;li&gt;Today_date: This indicates the current date, used to filter and display only trips available from today onwards. &lt;/li&gt;
&lt;li&gt;LastKey: Utilized for pagination purposes, facilitating the retrieval of subsequent sets of records beyond the initial limit.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_global_index(index,key,city,limit,today_date,lastkey=None):

    if lastkey:
        return query_table.query(
        IndexName=index,KeyConditionExpression=Key(
            key).eq(city),Limit=int(limit),FilterExpression=Attr('acceptto').gte(today_date),ExclusiveStartKey=json.loads(lastkey))

    return query_table.query(
        IndexName=index,KeyConditionExpression=Key(
            key).eq(city),Limit=int(limit),FilterExpression=Attr('acceptto').gte(today_date))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Users Sent/Received Requests&lt;/strong&gt;&lt;br&gt;
DynamoDB supports querying data with a key that begins with a specific value. When users send or receive requests in the Take IT app, these requests are stored in DynamoDB with statuses such as "pending," "accepted," "request," or "declined." This allows for efficient querying and retrieval of requests based on their status, enabling seamless management and tracking of request statuses within the application.&lt;/p&gt;

&lt;p&gt;The function looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;item: This refers to "pending," "accepted," "request," or "declined."
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query_table.query(KeyConditionExpression=Key("username").eq(username) &amp;amp; Key("created").begins_with(item))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's an example of how a user request is saved into DynamoDB in JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "username": "user2-466b-a4b9-94f90",
 "created":  "request_2022-10-15-22-35-18-213147_user1-4fb1dce7-6a50",
 "dtime": "2022-10-15-22-42-41-599518",
 "tripid": "2022-10-15-22-35-18-213147",
 "tstamp": 1669766400
},
{
 "username": "user1-4fb1dce7-6a50",
 "created": "pending_2022-10-15-22-35-18-213147_user2-466b-a4b9-94f90",
 "dtime": "2022-10-15-22-42-41-599518",
 "tripid": "2022-10-15-22-35-18-213147",
 "tstamp": 1669766400
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Remove Account&lt;/strong&gt;&lt;br&gt;
When a user decides to delete their account in the Take IT app, several steps are initiated:&lt;/p&gt;

&lt;p&gt;Querying User Data: All data associated with the user is queried from DynamoDB, including trip history, pending requests, and any other relevant information.&lt;/p&gt;

&lt;p&gt;Deleting User Data: Each record associated with the user's account is deleted from DynamoDB.&lt;br&gt;
This includes trip records, request records (both sent and received), and any other user-specific data stored in the database.&lt;/p&gt;

&lt;p&gt;Removing User from Cognito: The user is removed from the Cognito user pool, deleting their account and associated authentication tokens.&lt;/p&gt;

&lt;p&gt;The Cognito delete function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cognito = boto3.client('cognito-idp',region_name = region_name, verify=True)
cognito.admin_delete_user(UserPoolId= userpoolid, Username= username)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The delete function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def delete_records(username):
   items= query_table.query(
       KeyConditionExpression=Key("username").eq(username)
   )
   for i in range(len(items['Items'])):
       query_table.delete_item(
       Key={
           'username': username,
           'created' : items['Items'][i]['created']
           }
           )
   return True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>serverless</category>
      <category>dynamodb</category>
      <category>lambda</category>
    </item>
    <item>
      <title>Configure a privately hosted Git repository for EMR Studio</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Thu, 12 Oct 2023 09:13:02 +0000</pubDate>
      <link>https://dev.to/aws-builders/configure-a-privately-hosted-git-repository-for-emr-studio-2ih</link>
      <guid>https://dev.to/aws-builders/configure-a-privately-hosted-git-repository-for-emr-studio-2ih</guid>
      <description>&lt;p&gt;By default, you can access GitHub and GitLab from the studio workspace. If you are using a private Git repository, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/emr/latest/ManagementGuide/emr-studio-create-studio.html" rel="noopener noreferrer"&gt;Create a Studio&lt;/a&gt;: You should use a VPC with private subnets that have a NAT gateway to enable communication with the internet. &lt;/li&gt;
&lt;li&gt;Ensure that the security group for the default workspace has an outbound rule allowing HTTPS traffic on port 443 to the destination 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/emr/latest/ManagementGuide/interface-vpc-endpoint.html" rel="noopener noreferrer"&gt;Create VPC Endpoint&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make sure the endpoint uses private subnets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm that the security group attached to the endpoint allows inbound rules for HTTPS traffic on port 443 with the source set to either "Your VPC Address" or "0.0.0.0/0".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload the following configuration file into your Amazon S3 storage location that is used for your Studio in a folder called life-cycle-configuration:&lt;br&gt;
s3://BUCKET-NAME/life-cycle-configuration/configuration.json&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitServerDnsName - The DNS name of your Git server. For example "git.example.com".&lt;/p&gt;

&lt;p&gt;GitServerIpV4List - A list of IPv4 addresses that belong to your Git servers, the example VPC CIDR is 10.0.0.0/16, &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html" rel="noopener noreferrer"&gt;DNS&lt;/a&gt; is 10.0.0.2&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        "Type": "PrivatelyHostedGitConfig",
        "Value": [
            {
                "DnsServerIpV4": "10.0.0.2",
                "GitServerDnsName": "git.example.com",
                "GitServerIpV4List": [
                    "1.2.3.4"
                ]
            }
        ]
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"If you are facing issues, you may need to stop the workspace and start it again."&lt;/p&gt;

</description>
      <category>emr</category>
      <category>eks</category>
      <category>aws</category>
    </item>
    <item>
      <title>Fix Cert-Manager Conflict with EKS</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Wed, 29 Mar 2023 21:45:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/conflict-cert-manager-with-eks-2lbf</link>
      <guid>https://dev.to/aws-builders/conflict-cert-manager-with-eks-2lbf</guid>
      <description>&lt;p&gt;I was facing issue with multiple managed worker nodes running on EKS clusters. &lt;/p&gt;

&lt;p&gt;The issue was appearing randomly in different nodes, I cannot access the pods or get the logs by kubectl.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x509: cannot validate certificate for 10.0.83.153 because it doesn’t contain any IP SANs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kube API in the CloudWatch showing the following errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E0327 08:54:17.406029 11 status.go:71] apiserver received an error that is not an metav1.Status: &amp;amp;errors.errorString{s:"error dialing backend: x509: cannot validate certificate for 10.0.83.153 because it doesn't contain any IP SANs"}: error dialing backend: x509: cannot validate certificate for 10.0.83.153 because it doesn't contain any IP SANs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After investigating the issue with the AWS EKS support team, we found that cert-manager-webhook is causing the issue.&lt;br&gt;
Kubelet certificate chain is being used from cert-manager-webhook-ca.&lt;/p&gt;

&lt;p&gt;Run the following command on the non-working node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl s_client -connect localhost:10250 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECTED(00000003)
---
Certificate chain
 0 s:
   i:/CN=cert-manager-webhook-ca
---
Server certificate
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
subject=
issuer=/CN=cert-manager-webhook-ca
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command on the working healthy node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl s_client -connect localhost:10250 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONNECTED(00000003)
---
Certificate chain
 0 s:/O=system:nodes/CN=system:node:ip-10-0-31-151.eu-west-1.compute.internal
   i:/CN=kubernetes
---
Server certificate
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
subject=/O=system:nodes/CN=system:node:ip-10-0-31-151.eu-west-1.compute.internal
issuer=/CN=kubernetes
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cert-manager-webhook deployment uses port 10250 which is also used for kubelet.&lt;/p&gt;

&lt;p&gt;The solution is change the port of cert-manager-webhook to 10260.&lt;/p&gt;

&lt;p&gt;By setting webhook.securePort to 10260&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.10.0 \
  --set webhook.securePort=10260
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://cert-manager.io/docs/concepts/webhook/" rel="noopener noreferrer"&gt;https://cert-manager.io/docs/concepts/webhook/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://cert-manager.io/docs/installation/compatibility/#aws-eks" rel="noopener noreferrer"&gt;https://cert-manager.io/docs/installation/compatibility/#aws-eks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eks</category>
      <category>kubernetes</category>
      <category>openssl</category>
    </item>
    <item>
      <title>Integrate Keycloak and Kubeapps on AWS EKS</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Sun, 25 Sep 2022 09:47:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/integrate-keycloak-and-kubeapps-on-aws-eks-3fpm</link>
      <guid>https://dev.to/aws-builders/integrate-keycloak-and-kubeapps-on-aws-eks-3fpm</guid>
      <description>&lt;p&gt;In this article, I am going to show you how to integrate keycloak with kubeapps on AWS EKS.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/create-cluster.html" rel="noopener noreferrer"&gt;EKS Cluster&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Register a domain in &lt;a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html" rel="noopener noreferrer"&gt;route53&lt;/a&gt; or create a subdomain, ex: example.com &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Request a certificate from &lt;a href="https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html" rel="noopener noreferrer"&gt;ACM&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install Keycloak&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-release bitnami/keycloak --version 9.8.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No needs to use the service Loadbalancer for keycloak as we will use the Nginx ingress controller.&lt;br&gt;
Let's switch the service of keycloak to ClusterIP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl patch svc my-release-keycloak  -p '{"spec": {"type": "ClusterIP"}}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the admin password of keycloak&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get secrets/my-release-keycloak -o jsonpath='{.data.admin-password}' | base64 --decode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Install Nginx Ingress Controller
Update the AWS ACM certificate on the below installation
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install nginx ingress-nginx/ingress-nginx --set controller.service.type=LoadBalancer  --set controller.service.targetPorts.https=http --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-cert"="arn:aws:acm:eu-west-1:XXXX:certificate/XXXX-XXX-XXX-XXXX-XXXXX"  --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-backend-protocol"=http --set controller.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-ssl-ports"=443   --set-string controller.config.use-forwarded-headers="true" --version 4.2.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create ingress for keycloak
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-buffer-size: 10k
  name: keycloak
  namespace: default
spec:
  rules:
  - host: auth.example.com
    http:
      paths:
      - backend:
          service:
            name: my-release-keycloak
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login to the keycloak console &lt;a href="https://auth.example.com" rel="noopener noreferrer"&gt;https://auth.example.com&lt;/a&gt;, with username: &lt;strong&gt;user&lt;/strong&gt; and password in the keycloak installation step.&lt;/p&gt;

&lt;p&gt;Create a new realm, name it kubeapps&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Groups Claim&lt;/strong&gt;&lt;br&gt;
By default, there is no "groups" scope/claim. We will create a global client scope for groups.&lt;/p&gt;

&lt;p&gt;In the admin console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Client Scopes" from the left navigator menu&lt;/li&gt;
&lt;li&gt;Click on "Create" from the table (top right corner)&lt;/li&gt;
&lt;li&gt;Provide a name, ensure the protocol is set to "openid-connect" and that the option "Include in Token Scope" is on.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Once the client scope is created, you should be redirected to a page with several tabs. Navigate to the "Mappers" tab as we need to create a mapper to populate the value of the associated claim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the "Mappers" tab&lt;/li&gt;
&lt;li&gt;Click on "Create" from the table to create a new mapper&lt;/li&gt;
&lt;li&gt;Configure:&lt;/li&gt;
&lt;li&gt;Enter a name&lt;/li&gt;
&lt;li&gt;Select "Group Membership" as the claim type&lt;/li&gt;
&lt;li&gt;Enter "groups" as the token claim name&lt;/li&gt;
&lt;li&gt;Ensure the "Full group path" is OFF&lt;/li&gt;
&lt;li&gt;Keep the other knobs as ON&lt;/li&gt;
&lt;li&gt;Click ‘Save'&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Kubeapps Client on Keycloak&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Clients" from the left navigator&lt;/li&gt;
&lt;li&gt;Click "Create" from the table&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter an "id" and Save (e.g. kubeapps)&lt;br&gt;
Once created, configure the authentication as follows:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the protocol is set to "openid-connect"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the "Access Type" to be "confidential". This will add a new "Credentials" tab from which you can get the client secret&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure "Standard Flow Enabled" is enabled, this is required for the login screen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Direct Access Grants Enabled" can be disabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the "Valid Redirect URIs" field, enter "&lt;a href="https://kubeapps.example.com/*" rel="noopener noreferrer"&gt;https://kubeapps.example.com/*&lt;/a&gt;" as a placeholder. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Note: take copy of the secret in Credentials&lt;/p&gt;

&lt;p&gt;As for the cluster clients, we need to configure the client scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the "Client Scopes" tab&lt;/li&gt;
&lt;li&gt;Ensure the "email" scope is available either in the "Assigned Default Client Scopes" list or the "Assigned Optional Client Scopes" list&lt;/li&gt;
&lt;li&gt;The "groups" client scope should be available in the lists on the left. &lt;/li&gt;
&lt;li&gt;Add it either to the "Assigned Default Client 
Scopes" list or the "Assigned Optional Client Scopes" list.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Note the issuer URL from keycloak Realm&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Realm Settings →  Endpoints →  OpenID Endpoint Configuration then copy the issuer URL &lt;br&gt;
ex: &lt;a href="https://auth.example.com/realms/kubeapps" rel="noopener noreferrer"&gt;https://auth.example.com/realms/kubeapps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the EKS Cluster:&lt;/strong&gt;&lt;br&gt;
To the EKS cluster and then to Authentication&lt;/p&gt;

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

&lt;p&gt;Add the identity provider like the following:&lt;br&gt;
Issuer URL: &lt;a href="https://auth.example.com/realms/kubeapps" rel="noopener noreferrer"&gt;https://auth.example.com/realms/kubeapps&lt;/a&gt;&lt;br&gt;
Client ID: kubernetes&lt;br&gt;
Username claim: email&lt;br&gt;
Groups claim: groups&lt;/p&gt;

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

&lt;p&gt;the process takes around 20 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Back to Keycloak&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a new Client&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click "Clients" from the left navigator&lt;/li&gt;
&lt;li&gt;Click "Create" from the table&lt;/li&gt;
&lt;li&gt;Enter an "id" kubernetes and Save &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once created, configure the authentication as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure the protocol is set to "openid-connect"&lt;/li&gt;
&lt;li&gt;Configure the "Access Type" to be "confidential"&lt;/li&gt;
&lt;li&gt;turn on the "Standard Flow Enabled"&lt;/li&gt;
&lt;li&gt;Ensure "Direct Access Grants Enabled" is enabled, as this is how we can get the tokens via API&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;p&gt;&lt;strong&gt;On Kubeapps client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clients → Mappers → Create &lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Create a group&lt;/strong&gt;&lt;br&gt;
The group name is admin&lt;br&gt;
Groups --&amp;gt; New&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Add users to join the group&lt;/strong&gt;&lt;br&gt;
Users --&amp;gt; Your User --&amp;gt; Groups --&amp;gt; choose admin --&amp;gt; join&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Install Kubeapps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Update the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clientSecret gets it from the above step Kubeapps Client on keycloak&lt;/li&gt;
&lt;li&gt;issuer-url and redirect-url for your domain.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami

helm install kubeapps bitnami/kubeapps \
  --set authProxy.enabled=true \
  --set authProxy.provider=oidc \
  --set authProxy.clientID=kubeapps \
  --set authProxy.clientSecret=&amp;lt;secret client credentials in the kubeapps client&amp;gt; \
  --set authProxy.cookieSecret=$(echo "not-good-secret" | base64) \
  --set authProxy.extraFlags="{--cookie-secure=false,--oidc-issuer-url=https://auth.example.com/realms/kubeapps,--redirect-url=https://kubeapps.example.com/oauth2/callback}" \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Create an ingress for kubeapps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: 10k
  name: kubeapps
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: kubeapps.example.com
    http:
      paths:
      - backend:
          service:
            name: kubeapps
            port:
              number: 80
        pathType: ImplementationSpecific

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important to add proxy-buffer-size on the ingress controller otherwise you will get an error that says "upstream sent too big header while reading response header from upstream"&lt;/p&gt;

&lt;p&gt;Create a new user on the keycloak, make sure you add email, and Email Verified is on.&lt;/p&gt;

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

&lt;p&gt;The last step we need to create a role binding to access the Kubernetes cluster, this group has the same name admin as the keycloak group&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  managedFields:
  - apiVersion: rbac.authorization.k8s.io/v1
  name: keycloak-admin-group
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, log in to Kubeapps at &lt;a href="https://kubeapps.example.com" rel="noopener noreferrer"&gt;https://kubeapps.example.com&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;If you want to connect to EKS cluster by using OIDC&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install &lt;a href="https://github.com/int128/kubelogin" rel="noopener noreferrer"&gt;kubelogin&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to keycloak and then go back to the Kubernetes client we created. You will see a tab called Credentials, grab the client secret.&lt;/li&gt;
&lt;li&gt;Run the below command in your terminal to verify authentication to keycloak:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl oidc-login setup --oidc-issuer-url=https://auth.example.com/realms/kubeapps --oidc-client-id=kubernetes --oidc-client-secret=XXXXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Bind a cluster role&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create clusterrolebinding oidc-cluster-admin --clusterrole=cluster-admin --user='https://auth.example.com/realms/kubeapps#f5092396-d3b8-452c-9d1a-2b1dfbd58718'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Set up the kubeconfig&lt;/strong&gt;&lt;br&gt;
Use the same secret of Kubernetes client&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config set-credentials oidc \
      --exec-api-version=client.authentication.k8s.io/v1beta1 \
      --exec-command=kubectl \
      --exec-arg=oidc-login \
      --exec-arg=get-token \
      --exec-arg=--oidc-issuer-url=https://auth.example.com/realms/kubeapps \
      --exec-arg=--oidc-client-id=kubernetes \
      --exec-arg=--oidc-client-secret=XXXXXXX \
          --exec-arg=--oidc-extra-scope=groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify cluster access&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl --user=oidc get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Switch the default context to oidc&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config set-context --current --user=oidc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you have issues, you need to check if your token has added admin group:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"groups": [&lt;br&gt;
    "admin"&lt;br&gt;
  ]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L -X POST 'https://auth.example.com/realms/kubeapps/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=kubernetes' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_secret=XXXXXXX' \
--data-urlencode 'scope=openid email groups' \
--data-urlencode 'username=mohamed.radwan' \
--data-urlencode 'password=XXXXX' \
| jq -r .id_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;check your token by &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/vmware-tanzu/kubeapps/blob/main/site/content/docs/latest/howto/OIDC/OAuth2OIDC-keycloak.md" rel="noopener noreferrer"&gt;https://github.com/vmware-tanzu/kubeapps/blob/main/site/content/docs/latest/howto/OIDC/OAuth2OIDC-keycloak.md&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>eks</category>
      <category>keycloak</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
