<?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>Stop Paying for Availability You Don’t Actually Have</title>
      <dc:creator>Mohamed Radwan</dc:creator>
      <pubDate>Fri, 11 Sep 2026 09:34:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/stop-paying-for-availability-you-dont-actually-have-58ei</link>
      <guid>https://dev.to/aws-builders/stop-paying-for-availability-you-dont-actually-have-58ei</guid>
      <description>&lt;p&gt;Cloud platforms distribute workloads across Availability Zones (AZs) by default, for resilience. But cross-AZ traffic, replicated storage, and unnecessary spread of stateless and stateful workloads generate ongoing charges. &lt;br&gt;
For platforms that don’t need a strict multi-AZ SLA, intentionally co-locating an application’s compute and its stateful components &lt;br&gt;
not just the database, but any StatefulSet: message queues, search indices, caches, custom stateful services — inside one AZ can reduce infrastructure costs by more than 10%, depending on internal traffic volume and architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where the cost comes from
&lt;/h2&gt;

&lt;p&gt;Cross-AZ charges typically originate from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application pods talking to a StatefulSet (database, queue, cache, search index) in a different AZ&lt;/li&gt;
&lt;li&gt;EBS/PD-backed volumes located in a different AZ from the pods that use them&lt;/li&gt;
&lt;li&gt;Chatty internal APIs crossing AZ boundaries at high volume&lt;/li&gt;
&lt;li&gt;Monitoring/logging traffic unnecessarily spread across zones&lt;/li&gt;
&lt;li&gt;Replication traffic crossing AZs without a real availability requirement behind it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This traffic stays inside the region, but most providers still bill it as regional data transfer. On database- or queue-heavy platforms, this becomes a real monthly cost.&lt;/p&gt;
&lt;h2&gt;
  
  
  The basic optimization
&lt;/h2&gt;

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

&lt;p&gt;Any workload where the application tier repeatedly exchanges large volumes of data with a stateful backend — SQL/NoSQL databases, Kafka/RabbitMQ, Elasticsearch/OpenSearch, Redis, custom stateful services is a candidate for this optimization.&lt;/p&gt;
&lt;h2&gt;
  
  
  The storage reality: your StatefulSet is already single-AZ
&lt;/h2&gt;

&lt;p&gt;This is the part most cost discussions skip, and it’s the strongest technical argument for this pattern.&lt;br&gt;
Block storage such as AWS EBS is zonal.&lt;br&gt;
A StatefulSet pod backed by such a volume can only ever run in the AZ where its volume lives. &lt;br&gt;
Kubernetes cannot move it, and a scheduling rule cannot migrate it. It can only place other pods relative to it.&lt;/p&gt;

&lt;p&gt;That means: if you have not built real cross-AZ replication for that stateful workload (e.g., Postgres streaming replica, Kafka multi-AZ, Elasticsearch multi-zone cluster), then that workload is already exposed to a single-AZ failure domain today regardless of how many AZs your stateless application pods are spread across.&lt;/p&gt;

&lt;p&gt;So if AZ-2 (where your only Postgres/queue/cache instance lives) goes down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An application tier spread across AZ-1/2/3 still fails for all users, because every app pod depends on the same single-AZ backend.&lt;/li&gt;
&lt;li&gt;Spreading the app tier bought you nothing for this workload's availability; it only paid cross-AZ transfer fees while the backend was healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Co-locating the app pods with that StatefulSet in its AZ does not meaningfully change your real availability in this common case. It just removes the wasted cost of the app pods talking to it cross-AZ during normal operation. You are not trading availability for cost here; you are removing a cost that was never buying you the availability it appeared to.&lt;br&gt;
If you do have genuine multi-AZ stateful replication, this argument doesn’t apply.&lt;/p&gt;

&lt;p&gt;Going from single-AZ to multi-AZ typically moves you from ~99.5% to ~99.99%, roughly the difference between ~1.8 days/year and ~1 hour/year of potential downtime, for the redundant components. &lt;br&gt;
That gap is the real price of true HA. It only applies to components that are actually replicated across AZs, not to a stateless app tier sitting in front of a single-AZ backend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why dynamic policy (Kyverno) beats manual nodeAffinity
&lt;/h2&gt;

&lt;p&gt;nodeAffinity/nodeSelector are the underlying Kubernetes scheduling primitives, but they’re static and per-workload. Applied manually across many tenants/workloads, you’d need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the current AZ of each StatefulSet anchor per tenant&lt;/li&gt;
&lt;li&gt;Hardcode that AZ into every related Deployment&lt;/li&gt;
&lt;li&gt;Keep it updated whenever the StatefulSet pod is recreated in another AZ&lt;/li&gt;
&lt;li&gt;Avoid ever applying it to PVC-backed pods or DaemonSets
That’s manual, error-prone, and drifts over time. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kyverno centralizes it: at pod admission time it looks up the anchor’s live AZ and injects the constraint automatically, only for eligible stateless pods.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example — Generic anchor via label
&lt;/h2&gt;

&lt;p&gt;Recommended approach: label whichever StatefulSet pod should act as the AZ anchor (database, queue, cache, search node — anything), instead of hardcoding a name pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pin-stateless-pods-to-anchor-az&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;          &lt;span class="c1"&gt;# only mutate newly-created pods; never retroactive&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pin-to-anchor-zone&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;any&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Pod&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# 1. Namespace must opt in via a generic "tenant" label (any value)&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenant&lt;/span&gt;
          &lt;span class="na"&gt;apiCall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;urlPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/namespaces/{{request.namespace}}"&lt;/span&gt;
            &lt;span class="na"&gt;jmesPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata.labels."example.com/tenant-name"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
        &lt;span class="c1"&gt;# 1b. Is the namespace deactivated?&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deactivated&lt;/span&gt;
          &lt;span class="na"&gt;apiCall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;urlPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/namespaces/{{request.namespace}}"&lt;/span&gt;
            &lt;span class="na"&gt;jmesPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata.labels."deactivated"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
        &lt;span class="c1"&gt;# 2. Anchor = any running pod explicitly labelled as the AZ anchor&lt;/span&gt;
        &lt;span class="c1"&gt;#    (could be a database, queue, cache, search node, or any StatefulSet)&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;anchorNode&lt;/span&gt;
          &lt;span class="na"&gt;apiCall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;urlPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/namespaces/{{request.namespace}}/pods"&lt;/span&gt;
            &lt;span class="na"&gt;jmesPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items[?metadata.labels."example.com/az-anchor"=='&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[?status.phase=='&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Running'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[0].spec.nodeName&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
        &lt;span class="c1"&gt;# 3. Read that node's AZ&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zone&lt;/span&gt;
          &lt;span class="na"&gt;apiCall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;urlPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/v1/nodes"&lt;/span&gt;
            &lt;span class="na"&gt;jmesPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items[?metadata.name=='&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{anchorNode}}'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[0].metadata.labels."topology.kubernetes.io/zone"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
      &lt;span class="na"&gt;preconditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tenant&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NotEquals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deactivated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NotEquals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;anchorNode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NotEquals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;zone&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NotEquals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none"&lt;/span&gt;
          &lt;span class="c1"&gt;# skip DaemonSets (must run on every node)&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request.object.metadata.ownerReferences[?kind=='DaemonSet']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;length(@)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Equals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="c1"&gt;# skip ANY pod that mounts a PVC — protects the anchor itself and every other StatefulSet&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request.object.spec.volumes[?persistentVolumeClaim]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;length(@)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Equals&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;mutate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;patchStrategicMerge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;nodeSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;topology.kubernetes.io/zone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;zone&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the anchor label once, on whichever StatefulSet should define the AZ for that namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace&amp;gt; label pod &amp;lt;anchor-statefulset&amp;gt;-0 example.com/az-anchor&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  RBAC required
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno:pin-stateless-pods-to-anchor-az&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admission-controller&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno&lt;/span&gt;
    &lt;span class="na"&gt;app.kubernetes.io/part-of&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;namespaces"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nodes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rollout strategy
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Measure — cross-AZ transfer cost, which StatefulSets are heavy talkers, current pod/volume AZs.&lt;/li&gt;
&lt;li&gt;Classify — genuinely multi-AZ-replicated stateful workloads (keep spread) vs. single-AZ-bound ones (safe to co-locate around).&lt;/li&gt;
&lt;li&gt;Label anchors — tag the StatefulSet(s) that should define each namespace’s AZ.&lt;/li&gt;
&lt;li&gt;Pilot — apply the policy in TEST/DEV, restart a few Deployments, confirm placement and zero Pending pods.&lt;/li&gt;
&lt;li&gt;Validate — compare cross-AZ bytes and cost before/after; confirm latency/error rates unchanged.&lt;/li&gt;
&lt;li&gt;Expand gradually — roll out per tenant group; explicitly exclude any workload with a real multi-AZ HA requirement.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep genuinely multi-AZ-replicated stateful workloads spread — that's a deliberate, paid-for availability trade-off (roughly 99.5% → 99.99%, i.e., ~1.8 days/year → ~1 hour/year of potential downtime).&lt;/li&gt;
&lt;li&gt;For everything else, recognize that an unreplicated StatefulSet is already a single-AZ failure domain — co-locating its stateless clients with it doesn't reduce real-world availability, it just stops paying cross-AZ transfer fees for no benefit.&lt;/li&gt;
&lt;li&gt;Use Kyverno, not manual &lt;code&gt;nodeAffinity&lt;/code&gt;, to apply and maintain this at scale.&lt;/li&gt;
&lt;li&gt;Test in DEV/TEST first, then measure the actual savings and availability impact — don't assume a fixed percentage; calculate it from your own traffic and billing data.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>kyverno</category>
    </item>
    <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>
  </channel>
</rss>
