<?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: ndewijer</title>
    <description>The latest articles on DEV Community by ndewijer (@ndewijer).</description>
    <link>https://dev.to/ndewijer</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F922828%2F493e70d4-b1df-43cc-bcc7-507cfc9e2b7d.png</url>
      <title>DEV Community: ndewijer</title>
      <link>https://dev.to/ndewijer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ndewijer"/>
    <language>en</language>
    <item>
      <title>From Docker to Lambda: An AWS Admin's Journey into Python Applications</title>
      <dc:creator>ndewijer</dc:creator>
      <pubDate>Mon, 20 Jan 2025 14:33:07 +0000</pubDate>
      <link>https://dev.to/aws-builders/from-docker-to-lambda-an-aws-admins-journey-into-python-applications-24e4</link>
      <guid>https://dev.to/aws-builders/from-docker-to-lambda-an-aws-admins-journey-into-python-applications-24e4</guid>
      <description>&lt;p&gt;You know how it goes, you start with a simple Python script to automate some AWS tasks, then another one to parse some CloudWatch logs, and before you know it, you're asking Claude to explain what a metaclass is because the documentation might as well be written in Ancient Greek. That was me, Three months ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Got Here
&lt;/h2&gt;

&lt;p&gt;After years of writing Python scripts for AWS automation (and yes, I'm guilty of having one of those "does everything" scripts), I decided to build something proper. Between my own historic scripts, Stack Overflow, and Claude helping me understand Python's more esoteric features, I started to feel like I was finally getting this whole "software development" thing.&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%2Fe9f2e8sfmaotskzmqshd.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%2Fe9f2e8sfmaotskzmqshd.png" alt="Screenshot of the Investment Portfolio Manager" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Screenshot of the app. This is &lt;a href="https://github.com/ndewijer/Investment-Portfolio-Manager/blob/1.1.3/backend/app/seed_data.py" rel="noopener noreferrer"&gt;Seed&lt;/a&gt; data, not my real investments. (I wish)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You see, I got tired of managing multiple Excel spreadsheets tracking various investment fund portfolios. Every time I bought or sold shares, I had to manually update prices, calculate returns, and track dividends. As an AWS professional, the idea of manual updates made me cringe. We automate infrastructure, why not this?&lt;/p&gt;

&lt;p&gt;So I built my first real Python application: An Investment Portfolio Manager. Thanks to some help from our AI friends that did the heavy lifting on the front-end, via trial and error I learned about proper project structure, SQLAlchemy relationships and even some unit testing!&lt;/p&gt;

&lt;p&gt;The application handled everything: portfolio management, transaction tracking, dividend processing, and even automated price updates. I had it running beautifully in Docker on my home server, with separate containers for the Flask back-end, React front-end (yes, I learned a bit of JavaScript too, thank you Mr. LLM), and a SQLite database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Your hobby is also your job" Dilemma
&lt;/h2&gt;

&lt;p&gt;Being an AWS professional, running this on my home server felt wrong. I mean, I live and breath AWS at my day job, why am I managing containers on my own hardware? Plus, every time my home internet hiccuped, my wife would complain that she couldn't check her investment returns. (Not really. She's very happy with the app and understanding of "the process" &amp;lt;3)&lt;/p&gt;

&lt;p&gt;The easy way: ECS. I already had the docker-compose file:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;investment-portfolio-backend&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DB_DIR=/data/db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;LOG_DIR=/data/logs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOMAIN=${DOMAIN:-localhost}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/path/to/your/data:/data&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;

  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&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="s"&gt;./frontend&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOMAIN=${DOMAIN:-localhost}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USE_HTTPS=${USE_HTTPS:-false}&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;investment-portfolio-frontend&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DOMAIN=${DOMAIN:-localhost}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;USE_HTTPS=${USE_HTTPS:-false}&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;80:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;app-network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But then I started thinking like an AWS architect (and looking at the AWS pricing calculator):&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%2Flga9rwnn3ozy2dvf2sfh.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%2Flga9rwnn3ozy2dvf2sfh.png" alt="Image description" width="800" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The price update function only needs to run once a day.&lt;/li&gt;
&lt;li&gt;Same goes for our accessing it. We check it a few times a week. so why run (and pay for) 24/7 containers?&lt;/li&gt;
&lt;li&gt;The frontend is just static files, sounds like an S3 website to me&lt;/li&gt;
&lt;li&gt;API Gateway and Lambda to handle the back-end API calls&lt;/li&gt;
&lt;li&gt;Aurora Serverless for the relational data (portfolios, transactions)&lt;/li&gt;
&lt;li&gt;DynamoDB could store the price history (better than my SQLite price table) &lt;em&gt;Spoiler&lt;/em&gt;: I never got to this step. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, that's when I fell down the serverless rabbit hole. &lt;/p&gt;

&lt;p&gt;I've gone in the shallows of this specific diving pool before. I've built a small serverless app, &lt;a href="https://github.com/ndewijer/lambda-temperature-rug/" rel="noopener noreferrer"&gt;a temperature tracking project&lt;/a&gt; that was a collaboration with my wife. It pulls temperature data from KNMI (Dutch meteorological institute) and generates a color-coded table that my wife then used to create a temperature rug. Literally turning weather data into a crafting project!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Date       | Min.Temp | Min.Kleur   | Max.Temp | Max.Kleur   |
----------------------------------------------------------------
| 2023-03-01 |   -4.1°C | darkblue   |    7.1°C | lightblue  |
| 2023-03-02 |    1.3°C | blue       |    6.8°C | lightblue  |
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app could run either locally or through Lambda via API Gateway, taking parameters for start date, end date, and date ordering. It was a perfect starter project, combining AWS services with a practical (and creative!) real-world use case.&lt;/p&gt;

&lt;p&gt;Going from that simple Lambda function to a full Flask application with SQLAlchemy models, background jobs, and complex relationships is like trying to explain IAM policies to a developer; Make time in your agenda because it won't be quick and there's going to be some confusion along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Serverless Temptation
&lt;/h2&gt;

&lt;p&gt;So, bringing it back, I couldn't help but look at my beautiful containerized application humming along and think "this could be more... cloudy." I mean, we have all these amazing serverless services, and here I am, managing containers like it's 2015. &lt;/p&gt;

&lt;p&gt;The application was working great, don't get me wrong. But every time I was working in the AWS Console, those Lambda and API Gateway services were just sitting there, judging me. "Why aren't you using us?" they seemed to ask. "We could scale automatically, you know. No more container management..."&lt;/p&gt;

&lt;p&gt;So I did what any reasonable AWS admin would do. Completely re-architect my application into that of a serverless architecture. Because why make small, sensible changes when you can do a complete architectural overhaul? The original project only took 2 months, I'm sure this will be a breeze.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Database Dilemma
&lt;/h3&gt;

&lt;p&gt;First up was the database. I had the backend use SQLite, which worked great locally, but then reality hit: SQLite and Lambda aren't exactly best friends. &lt;/p&gt;

&lt;p&gt;Sure, you &lt;em&gt;could&lt;/em&gt; use SQLite in Lambda (and part of me really wanted to try, Only way I'm ever getting into Corey Quinn's blog.), but let's be reasonable here. Plus, I'd then need to maintain two code bases. One for the docker-based one and another for the Lambda one. &lt;/p&gt;

&lt;p&gt;Pass.&lt;/p&gt;

&lt;p&gt;I needed something that would play nice with all the SQLAlchemy knowledge I'd just painfully acquired and after much contemplation (and several cups of coffee) later, I realized I could attempt to actually use both! SQLAlchemy speaks PostgreSQL Aurora Serverless seems like a good fit here. So, we make a dual-handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Environment-aware database session manager.

    Why this approach:
    1. Flask App: Uses Flask-SQLAlchemy session management
    2. Lambda: Uses direct AWS database connections
    3. Connection pooling optimization
    4. Automatic cleanup of resources
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_flask_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="c1"&gt;# Check if running in Flask
&lt;/span&gt;        &lt;span class="c1"&gt;# Flask benefits:
&lt;/span&gt;        &lt;span class="c1"&gt;# - Integrated with Flask-SQLAlchemy
&lt;/span&gt;        &lt;span class="c1"&gt;# - Handles application context
&lt;/span&gt;        &lt;span class="c1"&gt;# - Uses Flask configuration
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;..models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Running in Lambda
&lt;/span&gt;        &lt;span class="c1"&gt;# Lambda benefits:
&lt;/span&gt;        &lt;span class="c1"&gt;# - Direct database connections
&lt;/span&gt;        &lt;span class="c1"&gt;# - Optimized for serverless
&lt;/span&gt;        &lt;span class="c1"&gt;# - No Flask dependency overhead
&lt;/span&gt;        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_aws_db_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_flask_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if code is running in a Flask context&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;has_app_context&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;has_app_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Lambda Learning Curve
&lt;/h3&gt;

&lt;p&gt;Next up was converting my Flask application to Lambda functions. How hard could it be, right? I mean, I've written plenty of Lambda functions before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But converting a full Flask application? That was a different story. My first attempt looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# My first attempt at converting a Flask route
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/portfolios&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_portfolios&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;portfolios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolios&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# So, I'm not even trying to keep the codebase singular and I'm already at this monstrosity
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;portfolios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# CORS... *sigh*
&lt;/span&gt;                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# UUID needs string conversion
&lt;/span&gt;                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;is_archived&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_archived&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;portfolios&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# CloudWatch, my old friend
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, this is terrible. And not maintainable. So, not wanting to repeat myself (The DRY principle see, I'm learning developer stuff!), I wrote a decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Log to CloudWatch because that's where I live now
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now my Lambda functions started looking much cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@lambda_response&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_portfolios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;portfolios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;portfolios&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that will only work for lambda. Now the original flask routes are broken. So, I wrote a decorator that would allow me to use the same routes for both Flask and Lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dual_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Decorator that supports both Flask routes and Lambda handlers.

    Benefits:
    1. Single source of truth for route definitions
    2. No conditional code in business logic
    3. Transparent handling of environment differences
    4. Easy testing of both modes
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Register Flask route if in Flask context
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nd"&gt;@functools.wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# Detect environment and adapt accordingly
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;
                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_request_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="n"&gt;flask_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_flask_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;flask_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_lambda_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flask_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the following combined functions to return the correct response, no matter what the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_lambda_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flask_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Convert Flask response to Lambda response format.

    Why this matters:
    1. Maintains API Gateway integration
    2. Preserves HTTP semantics
    3. Handles binary responses correctly
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flask_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flask_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flask_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_flask_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Convert Lambda event to Flask request.

    Why this matters:
    1. Allows reuse of Flask route handling code
    2. Maintains compatibility with Flask extensions
    3. Enables gradual migration of functionality
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requestContext&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requestContext&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;query_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;queryStringParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert query parameters to string format
&lt;/span&gt;    &lt;span class="n"&gt;query_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query_string&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;

    &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EnvironBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;http_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_environ&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;making it possible to use the same routes for both Flask and Lambda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dual_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/portfolios&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_portfolios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;db_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;portfolios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;portfolios&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Static Simplicity
&lt;/h3&gt;

&lt;p&gt;The front-end was actually the easiest part. All dynamic components are in the backend. S3 static website hosting and CloudFront? That's bread and butter. &lt;/p&gt;

&lt;p&gt;A simple script like this can upload your frontend to S3 and invalidate the CloudFront cache to force a refresh of the content you just uploaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This part I understand!&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;build/ s3://&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--delete&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--cache-control&lt;/span&gt; &lt;span class="s1"&gt;'public, max-age=31536000'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'index.html'&lt;/span&gt;

aws cloudfront create-invalidation &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--distribution-id&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CF_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--paths&lt;/span&gt; &lt;span class="s2"&gt;"/*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;After weeks of learning, coding, and occasionally questioning my life choices, I had successfully transformed my containerized application into a fully serverless architecture. I don't think I'll keep it online as I don't want to build the security around it or suffer a Denial of Wallet attack if the URL ever comes out, but I learned a lot and I'm proud of what I've built.&lt;/p&gt;

&lt;p&gt;Here's what I learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Python isn't just for scripts anymore (but my shell scripts still come in handy. Come see: &lt;a href="https://github.com/ndewijer/CodeSnippets" rel="noopener noreferrer"&gt;https://github.com/ndewijer/CodeSnippets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The AWS Free Tier is your friend during development&lt;/li&gt;
&lt;li&gt;CloudWatch Logs are still the best debugging tool&lt;/li&gt;
&lt;li&gt;Sometimes the "proper" way isn't the AWS way (and that's okay)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Would I do it again? Oh god no. But I'm glad I did it. My investment portfolio manager runs perfectly fine and secure on my private network but I loved the journey. I learned a ton about both Python and dual stack development.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>python</category>
    </item>
    <item>
      <title>Geo-restricting AWS Workspaces: Finding out how to cook parsnip soup</title>
      <dc:creator>ndewijer</dc:creator>
      <pubDate>Mon, 22 Jan 2024 13:13:20 +0000</pubDate>
      <link>https://dev.to/aws-builders/geo-restricting-aws-workspaces-finding-out-how-to-cook-parsnip-soup-1h05</link>
      <guid>https://dev.to/aws-builders/geo-restricting-aws-workspaces-finding-out-how-to-cook-parsnip-soup-1h05</guid>
      <description>&lt;p&gt;Once I spun up a virtual machine in Sydney to get a recipe from the MasterChef Australia website due to the site being restricted to just Australia. I could do this because of cloud computing. It made it possible to do work from anywhere on Earth as long as both ends have an internet connection.  &lt;/p&gt;

&lt;p&gt;Because while connectivity around the globe is becoming better, faster, and more prevalent, data sovereignty is becoming a significant issue for organisations. Be it the intellectual property of MasterChef recipes or the personal (health) data of residents in a country staying within certain geographic boundaries. And this is what I’ll elaborate on.  &lt;/p&gt;

&lt;p&gt;I worked for a British customer connected closely with the United Kingdom National Health Service (NHS), the central government-funded medical and health care services all things health in the United Kingdom. This organisation has access to very sensitive data, which falls under Tier 3 data as defined by the Alan Turing Data Safe haven tiers (1). &lt;/p&gt;

&lt;p&gt;These tiers, together with agreements and requirements from the NHS, the United Kingdom’s Government and third-party data providers can be distilled into a set-in-stone rule that any and all data within the customer’s Tier 3 is not allowed to leave the United Kingdom (UK).  &lt;/p&gt;

&lt;p&gt;In. Any. Way. &lt;/p&gt;

&lt;p&gt;The environment has all its resources placed in the AWS EU-West-2 region so that all data, instances and all ingress and egress into this environment are located within the United Kingdom. Data in S3 buckets, Data Analysis on EC2 instances, and ingress/egress of Data via secured upload methods, all authenticated and authorized via an AWS Managed Active Directory and again, all safely within the EU-West-2 region. &lt;/p&gt;

&lt;p&gt;For User Ingress, Amazon WorkSpaces is going to be used. Amazon WorkSpaces is a fully managed desktop virtualization service for Windows and Linux. The Workspaces will give access to a secured Windows environment using a remote desktop session via PC over IP (PCoIP) that meets all the requirements set by the customer and regulators. But, where all infrastructure required for this to work, from domain controllers to the WorkSpaces themselves are on British soil, there is no way to have checks in place to see if the users themselves are in the UK. &lt;/p&gt;

&lt;p&gt;By default, any users that can fully authenticate against the environment can connect to this environment from anywhere in the world. Long live cloud computing! Except when you’re not allowed to have data leave the country. Because, when we say that data cannot leave in any way, this also means the data, as pixels, on a screen of an RDP session. This means that an authorized user that works on this data daily is not allowed to do so when they are at a conference in Norway, for example.  &lt;/p&gt;

&lt;p&gt;Let’s delve into how to solve this. How can we prevent users from logging into their Workspace when not in the UK? How do we create geo-restriction for a solution that does not have any? &lt;/p&gt;

&lt;p&gt;First, we start by looking at the documentation and see how this solution works. (2)  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F0g060u9ljfj3lrtm3mgl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F0g060u9ljfj3lrtm3mgl.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Figure 1 – AWS Managed WorkSpaces using AWS Managed Active Directory &lt;/p&gt;

&lt;p&gt;We see that the solution initiates the connection with authentication from the client, via the AWS-managed Auth/Session gateway to the Directory Service that is managing authentication. After that completes successfully, the workspace streaming is a setup that users can actively work through. &lt;/p&gt;

&lt;p&gt;The entire flow is completely managed by Amazon Web Services (AWS). Users (or administrators, for that matter) cannot manipulate the stream of data at all. When the author’s streaming data is attempted to be redirected or streamed via third-party solutions, it breaks. This is a great design. We don’t want any man-in-the-middle attacks succeeding.  &lt;/p&gt;

&lt;p&gt;Only when the traffic has come through the AWS-managed parts and arrives at the customer’s VPC do we gain control of this traffic and can steer it using the standard methods like security groups, routing, and NACLs. But, at that point, any data we could use for geolocation purposes, like the user’s IP address, have been lost. &lt;/p&gt;

&lt;p&gt;So, what can we do?  &lt;/p&gt;

&lt;p&gt;The Amazon WorkSpaces client has a Proxy Server option, but this only applies to the auth traffic. The moment the WorkSpaces client tries to set up the streaming connection, only a direct connection to AWS is accepted. So, setting up a proxy server behind some form of geo-restriction is not going to work. &lt;/p&gt;

&lt;p&gt;Let’s look at the backend options within WorkSpaces. The two client-side options are:   &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trusted devices via certificates &lt;/li&gt;
&lt;li&gt;IP access control groups&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trusted devices only control &lt;strong&gt;what&lt;/strong&gt; devices can log into the Workspace but with IP Access control we can control &lt;strong&gt;where&lt;/strong&gt; a user can log in from. &lt;/p&gt;

&lt;p&gt;Trying to find out how many IP addresses / IP ranges are in use within the UK (4), we come to the lofty sum of 85.000 CIDR ranges. That is a bit more than what Amazon WorkSpaces supports. Amazon WorkSpaces documentation describes (5) that only 250 IP rules can be added to an Amazon WorkSpaces instance.  &lt;/p&gt;

&lt;p&gt;This inspired me to develop a solution. As I love cloud computing and it allows me to, if something doesn’t exist, build it myself. So, the solution I designed looks as follows: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fnqsjxcabggvd9ae9hgpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnqsjxcabggvd9ae9hgpa.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Figure 2 – High-level infrastructure design WorkSpaces geo-restriction &lt;/p&gt;

&lt;p&gt;While Amazon WorkSpaces does not have geo-restriction, AWS Web Application Firewall (WAF) does! WAF cannot be directly connected to workspaces, but we can use the IP Access Control List.  &lt;/p&gt;

&lt;p&gt;So, the design is:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AWS WAF with a geographic match rule for the United Kingdom. &lt;/li&gt;
&lt;li&gt;An Application Load Balancer with the WAF attached. &lt;/li&gt;
&lt;li&gt;A Lambda that will validate the user and then set their home IP in WorkSpaces. &lt;/li&gt;
&lt;li&gt;An identity provider that has credentials for the user, in this case, Cognito.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fx1a2w0akqy1yehuted55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fx1a2w0akqy1yehuted55.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Figure 3 – UML Workflow user experience &lt;/p&gt;

&lt;p&gt;Users going to the URL of the Application Load Balancer will have their IP checked by the WAF. If this fails, the user will not even have the opportunity to log in. But if the user is within the UK, the user can then sign in with their credentials. When that succeeds, the AWS Lambda will add the user’s IP to the access control list and the user can then log into their workspace. This entire workflow is shown in figure 3. &lt;/p&gt;

&lt;p&gt;Finally, returning to my initial paragraph: this solution is not foolproof. By bringing up a virtual machine in the right location or using a VPN, working around the geo-restriction is not incredibly difficult. But like all things security, you work in layers. The geo-restriction works together with the authentication mechanism to not only have a user in the right place but also know the correct information to reach the secured environment. &lt;/p&gt;

&lt;p&gt;In conclusion, a thank you to Network 10 for planting the seed of my geo-restriction design and thank you for not having the recipe behind a login or else I would never have been able to eat this wonderful parsnip soup (6) my wife made. &lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://www.turing.ac.uk/research/research-projects/data-safe-havens-cloud" rel="noopener noreferrer"&gt;https://www.turing.ac.uk/research/research-projects/data-safe-havens-cloud&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;    &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/best-practices-deploying-amazon-workspaces/scenario-3-standalone-isolated-deployment-using-aws-directory-service-in-the-aws-cloud.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/whitepapers/latest/best-practices-deploying-amazon-workspaces/scenario-3-standalone-isolated-deployment-using-aws-directory-service-in-the-aws-cloud.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;    &lt;a href="https://docs.aws.amazon.com/workspaces/latest/adminguide/workspaces-port-requirements.html#client-application-ports" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/workspaces/latest/adminguide/workspaces-port-requirements.html#client-application-ports&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;    &lt;a href="https://www.ip2location.com/free/visitor-blocker" rel="noopener noreferrer"&gt;https://www.ip2location.com/free/visitor-blocker&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;    &lt;a href="https://aws.amazon.com/workspaces/faqs/" rel="noopener noreferrer"&gt;https://aws.amazon.com/workspaces/faqs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;   &lt;a href="https://10play.com.au/masterchef/recipes/parsnip-soup-with-stilton-and-bacon-crumb/r190614fckou" rel="noopener noreferrer"&gt;https://10play.com.au/masterchef/recipes/parsnip-soup-with-stilton-and-bacon-crumb/r190614fckou&lt;/a&gt; &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This blog was originally published on &lt;a href="https://helecloud.com/blog/geographic-restrictions-workspaces-finding-out-how-to-cook-parsnip-soup/" rel="noopener noreferrer"&gt;https://helecloud.com/blog/geographic-restrictions-workspaces-finding-out-how-to-cook-parsnip-soup/&lt;/a&gt; but has since then gone down due to Helecloud's acquisition. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>workspaces</category>
      <category>georestriction</category>
      <category>security</category>
    </item>
    <item>
      <title>Scaling Identity Access Management: From Startups to Enterprises with AWS Solutions - Part 2</title>
      <dc:creator>ndewijer</dc:creator>
      <pubDate>Mon, 24 Apr 2023 10:57:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-2-52m9</link>
      <guid>https://dev.to/aws-builders/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-2-52m9</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/aws-builders/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-1-4k9c"&gt;In Part 1&lt;/a&gt;, we discussed the benefits of AWS Organizations, such as centralized billing, organisational units (OUs), tagging policies, and service control policies (SCP), which provide enhanced security and standardization across AWS accounts. We also touched on AWS IAM Identity Center's capabilities, including integration with other Identity Providers (IdP) like Azure or Okta and the introduction of Attribute-based Access Control (ABAC). In Part 2, we'll explore how to leverage AWS Organizations, IAM Identity Center, and other AWS services together for easy, secure, and flexible access to your AWS environment&lt;/p&gt;

&lt;h2&gt;
  
  
  The meat and potatoes
&lt;/h2&gt;

&lt;p&gt;Managing resource access with Identity Center, Tagging and SCP&lt;br&gt;
The corner stone of this solution is the following IAM Policy condition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;"ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This policy line is comparing a dynamic resource tag to a dynamic principal tag. In short, this policy is Incredibly flexible and this section will explain why.&lt;/p&gt;

&lt;p&gt;Lets start with a high-level diagram. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fs5ofxjvilf6zam1fq7mb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fs5ofxjvilf6zam1fq7mb.png" alt="Figure 11 - IAM Identity Center synchronization and authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This diagram describes a user called John Smith within the Azure AD of “Company”. This organisation has setup AWS IAM Identity Center with SAML and SCIM and John is being synced into Identity Center. Further, John is a member of the Security group Developers within AzureAD. &lt;br&gt;
The admins of the organisation have setup a permission set granting Administrator rights and have provisioned this permission set into their workload account for the Developer group to use. &lt;br&gt;
In short, John now has admin rights to the AWS Workload account with his AzureAD user credentials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fgkj8kn3iggo7qlwufnzv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fgkj8kn3iggo7qlwufnzv.png" alt="Figure 12 - Identity Center Permissions - Detailed on workload account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The workload account that John has logged into with his administrator rights has an EC2 instance that belongs to the Finance department. Looking at the rules “Deny unless Allow unless Deny” we can infer that John can do anything and everything with this EC2 instance. &lt;/p&gt;

&lt;p&gt;Now, let’s bring in the condition &lt;code&gt;"ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fy2mytfpf80rbjtm7qv1b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fy2mytfpf80rbjtm7qv1b.png" alt="Figure 13 - Identity Center Permissions - ABAC policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The EC2 instance has a tag that defines the Department as Finance. By modifying the policy with a condition to only allow admin access if the user’s department matches that of the Instance’s department tag, as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": "*",
          "Resource": "*",
          "Condition": {
              "StringEquals": {
                  "ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"
              }
          }
      }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can define that they are an administrator but within a certain scope. Do be aware that this policy removes an allow so the rights for this now exists as an implicit deny which means that another policy could still grant access. To block any possible allows, the policy could be rewritten as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEqualsIfExists": {
          "ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This places the policy in the explicit deny instead of implicit. &lt;br&gt;
 &lt;br&gt;
Do be aware that the above policy might break many things as not all resources can be tagged. Especially list and describe actions will be affected. A better way of specifying the policy for our EC2 instance example would be to declare the actions 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;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": [
        "ec2:delete*",
        "ec2:modify*",
        "ec2:stop*",
        "ec2:start*",
        "ec2:create*",
        "ec2:associate*",
        "ec2:terminate*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEqualsIfExists": {
          "ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"
        }
      }
    }
  ]
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Web federated user
&lt;/h2&gt;

&lt;p&gt;The core of AWS IAM Identity Center works via the federated user architecture specifically, web identity federation. This login method has existed for a long time and is also used by Amazon Cognito to authenticate. Keeping this in mind will make working with federated identities that are passed to the AWS accounts by Identity Center easier.&lt;/p&gt;

&lt;p&gt;When the user ‘arrives’ in the workload AWS account, only a few bits of ‘identifying information’ are left that can be read and worked with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ aws sts get-caller-identity
{
    "UserId": "AROA4XGUTKGXSNEJDUOXZ:john@company.com",
    "Account": "456789012346",
    "Arn": "arn:aws:sts::456789012346:assumed-role/AWSReservedSSO_AdministratorAccess_bff86352c1cc681d/john@company.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;UserId, which replaces UserName when a user is federated. This contains the RoleID of the assumed role which will start with “AROA” and the username.&lt;/li&gt;
&lt;li&gt;FriendlyName, which is the last part of the UserID, in this case “&lt;a href="mailto:john@company.com"&gt;john@company.com&lt;/a&gt;”&lt;/li&gt;
&lt;li&gt;Account, which is the AWS account ID the user is logged into. &lt;/li&gt;
&lt;li&gt;Arn, which is the full Amazon Resource name of the principal user within the account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;UserId, FriendlyName and the PrincipalArn called in an IAM policy by using the&lt;br&gt;
 &lt;code&gt;AWS:userID&lt;/code&gt;, &lt;code&gt;AWS:friendlyName&lt;/code&gt; or &lt;code&gt;AWS:PrincipalArn&lt;/code&gt; variables respectively. &lt;/p&gt;

&lt;p&gt;These four items are the main identifying variables for a federated user. Identity Center however, also passes along the attributes of the federated user defined in Attribute-based Access Control as principal tags and this is what makes it possible to compare the principal tags against resource tags!&lt;/p&gt;

&lt;p&gt;At the time of writing, there is no API call that can present a list of the full AWS session tags. So checking if a federated user has received the right tags into their account is a bit convoluted. A work around to this is looking up the &lt;code&gt;“AssumeRoleWithSAML”&lt;/code&gt; event in CloudTrail of the account being logged into.&lt;/p&gt;

&lt;p&gt;Such an event looks as follows and is split up into three main sections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    “eventVersion”: “1.08”,
    “userIdentity”: {
        “type”: “SAMLUser”,
        “principalId”: “lUqu3Gchksa6MnzH4DmnCtbi8nA=:john@company.com”,
        “ sername”: “john@company.com”,
        “identityProvider”: “lUqu3Gchksa6MnzH4DmnCtbi8nA=”
    },
    “eventTime”: “2022-10-21T14:25:20Z”,
    “eventSource”: “sts.amazonaws.com”,
    “eventName”: “AssumeRoleWithSAML”,
    “awsRegion”: “eu-west-1”,
    “sourceIPAddress”: “xx.xx.xx.xx”,
    “userAgent”: “aws-internal/3 aws-sdk-java/1.12.291 Linux/4.14.287-148.504.amzn1.x86_64 OpenJDK_64-Bit_Server_VM/11.0.16+9-LTS java/11.0.16 vendor/Amazon.com_Inc. cfg/retry-mode/standard”,
The first section is the event information. Who is making the request, against what service, which region, when, etc.

“requestParameters”: {
        “sAMLAssertionID”: “x”,
        “roleSessionName”: “john@company.com”,
        “principalTags”: {
            “Department”: “Development”
        },
        “roleArn”: “arn:aws:iam::456789012346:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_AdministratorAccess_bff86352c1cc681d”,
        “principalArn”: “arn:aws:iam::456789012346:saml-provider/AWSSSO_6a25cda37e6197d5_DO_NOT_DELETE”,
        “durationSeconds”: 28800
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next comes the request parameters these contain variables presented to the service. For the request itself, the &lt;code&gt;sAMLAssertionID&lt;/code&gt;, &lt;code&gt;roleSessionName&lt;/code&gt;, &lt;code&gt;roleArn&lt;/code&gt; and &lt;code&gt;principalArn&lt;/code&gt; are the most important components but within this we can also see the principal tags being passed through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“principalTags”: {
   “Department”: “Development”
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, the service responds with what has been issued to the user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“responseElements”: {
        “credentials”: {
            “accessKeyId”: “ASIA4xxx”,
            “sessionToken”: “xxx”,
            “expiration”: “Oct 21, 2022, 10:25:19 PM”
        },
        “assumedRoleUser”: {
            “assumedRoleId”: “AROA4XGUTKGXSNEJDUOXZ:john@company.com”,
            “arn”: “arn:aws:sts::456789012346:assumed-role/AWSReservedSSO_AdministratorAccess_bff86352c1cc681d/john@company.com”
        },
        “packedPolicySize”: 1,
        “subject”: “john@company.com”,
        “subjectType”: “persistent”,
        “issuer”: “https://portal.sso.eu-west-1.amazonaws.com/saml/assertion/ODc0NDxxxx”,
        “audience”: “https://signin.aws.amazon.com/saml”,
        “nameQualifier”: “lUqu3Gchksa6MnzH4DmnCtbi8nA=”
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Especially the “assumedRoleId” is important here as that is the main “userId” that the user identifies itself with inside the AWS account.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding oomph to IAM
&lt;/h2&gt;

&lt;p&gt;In the last chapter we setup IAM policies with IAM Identity Center to only allow users to work with resources that they ‘own’, so to speak. We’ve done this by defining a policy that only allows actions on a resource if the principal making the request has the same department tag value as the resource. &lt;/p&gt;

&lt;p&gt;Now let’s add an extra layer of enforcement to make sure resources actually have this tag!&lt;/p&gt;

&lt;p&gt;All credits go to Arun Chandapillai and Shak Kathir who have written a blog about this exact subject. A link to the blog can be found in the sources.&lt;/p&gt;

&lt;p&gt;Within the AWS Organizations, three types of policies can be defined that enforce certain behaviour and/or restrictions on its member accounts. Backup policies, Tagging policies and Service control policies (SCPs).&lt;/p&gt;

&lt;p&gt;By creating a tagging policy that looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  “tags”: {
    “costcenter”: {
      “tag_key”: {
        “@@assign”: “department”
      },
      “tag_value”: {
        “@@assign”: [
          “Development”,
          “Finance”,
          “Sales”,
          “Legal”
        ]
      },
      “enforced_for”: {
        “@@assign”: [
          “ec2:instance”,
          “ec2:volume”
        ]
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And attaching it to either an account to just effect that account or an OU to affect all accounts in that OU and its children, it will enforce that when the tag department is used on an EC2 instance or volume, it must comply with having a specific value. &lt;br&gt;
The challenge here is that this policy does not prevent the tag from not being created in the first place and this is where an SCP comes in.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:RunInstances",
        "ec2:CreateTags",
        "ec2:DeleteTags"
      ],
      "Resource": [
        "arn:aws:ec2:*:*:instance/*",
        "arn:aws:ec2:*:*:volume/*"
      ],
      "Condition": {
        "StringNotEquals": {
            "ec2:ResourceTag/department": "${aws:PrincipalTag/department}"
        },
        "Null": {
            "ec2:ResourceTag/department": false
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By creating the following SCP and attaching it to the same accounts, EC2 instances or EC2 volumes cannot be created without having the department tag attached to the resources. The tags can also not be (re)created by someone that’s not from the same department nor can it be removed at all.&lt;/p&gt;

&lt;p&gt;The combination of the tagging policy forcing a specific value for resource tag, enforcing newly created resources to have this tag and then blocking access to modify the resource tag will ensure that resources in scope will always meet the tagging requirement.&lt;/p&gt;

&lt;p&gt;Then using the IAM policies to only allow access to resources if they match their department tag with the principal’s department tag will guarantee only the right access is provided. &lt;/p&gt;

&lt;h2&gt;
  
  
  Managing organisation access with Identity Center and SCP
&lt;/h2&gt;

&lt;p&gt;AWS advises that, wherever possible, as many separate AWS accounts should be used within your AWS Organization (AWS Org) as possible and workable. “An AWS account provides natural security, access and billing boundaries for your AWS resources, and enables you to achieve resource independence and isolation.”&lt;/p&gt;

&lt;p&gt;Following this advice, we can break accounts into out different ways.&lt;br&gt;
Several examples are a workload-based design:&lt;br&gt;
&lt;a href="https://media.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%2Fqiupwdtrsxj6v2i09mgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqiupwdtrsxj6v2i09mgi.png" alt="Figure 14 - Workload based OU structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or a prod/non prod design:&lt;br&gt;
&lt;a href="https://media.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%2Fjo3d2gn8iw4hx6lmhv7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjo3d2gn8iw4hx6lmhv7r.png" alt="Figure 15 - Production based OU structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or a department style setup:&lt;br&gt;
&lt;a href="https://media.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%2F2o8m2odybhtzoi0ol5kk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2o8m2odybhtzoi0ol5kk.png" alt="Figure 16 - Department based OU structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The options here are numerous and each organisation should take care to make an OU design that fits for their purpose.&lt;/p&gt;

&lt;p&gt;By splitting the OUs in the way that works for the organisation, creating specialised policies becomes easier. &lt;/p&gt;

&lt;p&gt;We’ve spoken about how AWS IAM Identity center provides access to users by linking them directly or via a group to an AWS account via a permission set. &lt;/p&gt;

&lt;p&gt;What is also possible within Identity center is to delegate access to another AWS account. For instance, a dedicated security account. &lt;/p&gt;

&lt;p&gt;By setting up access permissions in such a way, users that cannot access the master billing account can access Identity center from the Delegated Administrator account. &lt;br&gt;
These two components, a delegated Identity center and SCPs can combine to give an extra layer of security to who can and cannot access the AWS accounts within the AWS Org.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fc6xw06ofnejxucqfgpwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fc6xw06ofnejxucqfgpwk.png" alt="Figure 17 - AWS Org policies influence at different OU levels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within this example, the following SCP is assigned to the Development OU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "ForAllValue:StringNotEquals": {
          "aws:PrincipalTag/Department": [
            "Development",
            "Audit"
          ]
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy will add a second layer of ‘defence’ that will prevent any user that is not part of the Development or Audit department to perform any action within the accounts under the Development OU. &lt;/p&gt;

&lt;p&gt;Even if, for an example, a user/group from the Operations department is assigned to a development account, the SCP will block usage of this account as the department is defined in the organisations identity provider. &lt;/p&gt;

&lt;p&gt;And due to the SCP and Identity Center running in different accounts, these two vectors of security can be kept separate. &lt;/p&gt;

&lt;h2&gt;
  
  
  Outro
&lt;/h2&gt;

&lt;p&gt;Combining AWS Organizations with AWS IAM Identity Center enables organisations to securely grant their teams access to AWS accounts and resources while utilising their existing Identity Access Management solution.&lt;/p&gt;

&lt;p&gt;This native toolset is ideal for organisations as they begin working with AWS, providing essential guardrails and tools. Additional services like AWS GuardDuty, Inspector, and AWS Config help address security and compliance needs, while AWS Billing offers insights into costs across the AWS estate. These services effortlessly scale with your organisation in AWS, regardless of its size.&lt;/p&gt;

&lt;p&gt;Nick de Wijer – Principal AWS platform architect. &lt;br&gt;
 &lt;/p&gt;

</description>
      <category>awsorganizations</category>
      <category>iamidentitycenter</category>
      <category>accessmanagement</category>
      <category>scalability</category>
    </item>
    <item>
      <title>Scaling Identity Access Management: From Startups to Enterprises with AWS Solutions - Part 1</title>
      <dc:creator>ndewijer</dc:creator>
      <pubDate>Mon, 24 Apr 2023 10:57:07 +0000</pubDate>
      <link>https://dev.to/aws-builders/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-1-4k9c</link>
      <guid>https://dev.to/aws-builders/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-1-4k9c</guid>
      <description>&lt;p&gt;Identity access management (IAM) becomes increasingly complex as organizations grow from startups to large enterprises. Initially, with a small team, managing access and identity is simple, but as the company expands, so does the need for enhanced IAM systems. New hires, department restructuring, and the introduction of cloud platforms like AWS further complicate IAM. Learn how AWS IAM Identity Center and Service Control Policies via AWS Organizations can help streamline user and group management, while maintaining robust security.&lt;/p&gt;

&lt;p&gt;Before going deep in hooking up all kinds of different services together, let’s begin with a bit of context. To put all the different services and acronyms in their right place. &lt;br&gt;
If you’re familiar with the relevant AWS services, you can skip to &lt;a href="https://dev.to/ndewijer/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-2-52m9"&gt;part 2 of the blog&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  IAM Policies 101
&lt;/h2&gt;

&lt;p&gt;I often summarise access control in AWS with this statement.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Denied&lt;/strong&gt; unless &lt;strong&gt;Allowed&lt;/strong&gt; unless &lt;strong&gt;Denied&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What this means is that, by default, an entity within AWS has no rights to do anything unless these rights have been given AND, and this is the important part, they have not been explicitly denied.&lt;/p&gt;

&lt;p&gt;So, written out fully, it’s &lt;strong&gt;Implicitly&lt;/strong&gt; Denied until &lt;strong&gt;Explicitly&lt;/strong&gt; Allowed until &lt;strong&gt;Explicitly&lt;/strong&gt; Denied.&lt;/p&gt;

&lt;p&gt;Within AWS what is allowed or denied to an entity is defined within Identity Access Management (IAM) policies. These policies are JSON formatted documents that define what a “principal” is allowed to do or, more importantly, not allowed to do within a certain context. &lt;br&gt;
This principal is the initiator of the action and can be an IAM user or an application because within AWS, nothing and no-one is allowed to do anything without an IAM policy allowing it to do so.&lt;/p&gt;

&lt;p&gt;Policies do not live in a vacuum. Policies are attached to things. These things differ depending on the policy. &lt;br&gt;
If the policy is identity-based, they are assigned to roles or groups which in turn are assumed by (roles) or assigned to (groups) users or other AWS resources.&lt;br&gt;
If its resource based, the policy is attached directly to an AWS resource that will then be used to determine what rights the interacting principal has on this resource. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c9qBA57B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j68jvjv04eaqym93s9zj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c9qBA57B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j68jvjv04eaqym93s9zj.png" alt="Figure 1 - Identity and resource based IAM policies" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The components of these policies are as follows:&lt;br&gt;
• Version: The policy language version so that AWS can correctly parse the data defined in the policy. Two exist, the older 2008-10-17 and the current 2012-10-17.&lt;br&gt;
• Statement: Contains one or multiple policy statements. &lt;/p&gt;

&lt;p&gt;Within the statement, policies are defined with the following.&lt;/p&gt;
&lt;h4&gt;
  
  
  Principal
&lt;/h4&gt;

&lt;p&gt;Defines the principal (initiator of the action) that this policy statement applies to.&lt;/p&gt;

&lt;p&gt;A principal, when defined must map against an IAM or STS user, group or role or an AWS Service. These can be as broad as an entire AWS account or as narrow as a single IAM user.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
AWS Account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Principal": {"AWS": "arn:aws:iam::045676890123:root"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Principal": {"Service": "delivery.logs.amazonaws.com"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;STS Assumed Role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Principal": {"AWS": "arn:aws:sts::045676890123:assumed-role/{rolename}"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS Role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Principal": {"AWS": "arn:aws:iam::045676890123:role/{rolename}"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the policy is used in an identity-manner, it is not required to be defined because it is inferred by the principal the policy is attached too. &lt;br&gt;
If the attached is used as a resource-based object however, it is required to define what principals are allowed to interact with the resource.&lt;/p&gt;
&lt;h4&gt;
  
  
  Effect
&lt;/h4&gt;

&lt;p&gt;Does the policy either &lt;code&gt;“Allow”&lt;/code&gt; or &lt;code&gt;“Deny”&lt;/code&gt; the action defined in the policy.&lt;/p&gt;
&lt;h4&gt;
  
  
  Action
&lt;/h4&gt;

&lt;p&gt;Define an action within AWS. &lt;br&gt;
Actions are defined as &lt;code&gt;“{service}:{action}”&lt;/code&gt; where part or the entirety can be replaced with a wildcard *. Examples are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Action": "s3:PutObject"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This action allows for the writing of an object into S3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Action": "kms:*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This action allows every action within the Key Management Service (KMS)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Action": "*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This action allows everything. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If not secured with either a Resource or a Condition statement, it will allow full access to every part of an AWS account or resource.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actions can be defined as &lt;code&gt;“Action”&lt;/code&gt; or &lt;code&gt;“NotAction”&lt;/code&gt; creating a white or blacklist respectively. Combined with &lt;code&gt;Effect:Deny&lt;/code&gt;, double-negative style allows can be created. We will zoom in on this later.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resource
&lt;/h4&gt;

&lt;p&gt;Define the AWS resource that the Action is limited too. This resource must be defined with an AWS ARN and one or more wildcards (*) can be used to broaden the scope of the resource(s) under the policy.&lt;/p&gt;

&lt;p&gt;Examples are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Resource": "arn:aws:s3:::accesslogs/*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which defines all objects within an S3 bucket called “accesslogs”&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Resource": "arn:aws:kms:eu-west-1:045676890123:key/87132241-9a03-4c89-a723-af0b43aea2bc7"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which defines a specific KMS key within account 045676890123 in AWS Region eu-west-1&lt;br&gt;
&lt;code&gt;"Resource": "*"&lt;/code&gt;&lt;br&gt;
Which defines all resources&lt;/p&gt;
&lt;h4&gt;
  
  
  Condition
&lt;/h4&gt;

&lt;p&gt;Conditions is an optional statement that can contain one or more sub-statements where further allowances or limitations can be made on top of Principal/Action and Resource.&lt;/p&gt;

&lt;p&gt;Within conditions, logic checks can be performed. For example, comparing values from the calling principal to the targeted resource. Another often used condition is to check (and then block) for unencrypted traffic. &lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4&gt;
  
  
  Full Policies
&lt;/h4&gt;

&lt;p&gt;Bringing all these together will create policies as these two examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This identity-based policy allows administrator rights to the entire AWS Account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Principal": {
                "AWS": "arn:aws:iam::045676890123:root"
            },
            "Effect": "Allow",
            "Action": "S3:PutObject",
            "Resource": "arn:aws:s3:::accesslogs/*",
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "true"
                }
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resource-based policy allows all principals from the account 045676890123 to write into the bucket “access logs” as long as the connection is encrypted.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Bringing it home
&lt;/h3&gt;

&lt;p&gt;When a request is made within AWS all the policies attached to the identity requesting and the resources requested from are taken and combined into a single policy. Then, if at least one “Allow” to the resource and no “Deny” is found, access is given.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WfOMiG8O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7t7exxf09th95tsadfm4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WfOMiG8O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7t7exxf09th95tsadfm4.jpg" alt="Figure 2 - IAM merging and analysing resource and identity-based policies" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If, however, a “Deny” is found, the request will be denied, even though an “Allow” for that same request exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ySf2NDjE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjkzdysjf0g8lxja4l2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ySf2NDjE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjkzdysjf0g8lxja4l2w.png" alt="Figure 3 - IAM Denying policy due to deny statement found analysing combined policies" width="800" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Organizations (Service name, further abbreviated to AWS Org) is designed to be used by organisations (EN-UK spelling) to centrally manage AWS accounts under their ownership more effectively.&lt;/p&gt;

&lt;p&gt;It allows for grouping AWS accounts in a hierarchical method using one or multiple levels of organizational units (OU) based on the requirements of the organisation. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vHTL0PFP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pek3sbf2qhztux8boci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vHTL0PFP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0pek3sbf2qhztux8boci.png" alt="Figure 4 - Example AWS Org OU structure" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These OUs will house one or more AWS accounts and can have several types of policies attached to either the OU or accounts directly to allow management on a high or granular level. The available policy types are.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backup policy – Ensure consistency in how AWS Backup plans are implemented&lt;/li&gt;
&lt;li&gt;Tag policies – Standardise tags on all supported resources&lt;/li&gt;
&lt;li&gt;Service control Policies (SCP) – Control IAM permissions within the AWS account&lt;/li&gt;
&lt;li&gt;AI services opt-out policies – Control what AI Services can store and what  content within an account it can use to do its job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F9LmNT0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egfy3nq4wznj7q6hs133.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F9LmNT0l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egfy3nq4wznj7q6hs133.png" alt="Figure 5 - Visual representation of AWS Org policies' influence in an OU structure" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a policy is applied to an OU, the policy will affect all child OUs and AWS accounts within those OUs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service control policies (SCP)
&lt;/h2&gt;

&lt;p&gt;Service control policies (SCP) introduce an extra level of securing AWS accounts by granting or denying access to resources.&lt;/p&gt;

&lt;p&gt;We’ve spoken about IAM policies. How they are constructed and how identity and resource-based policies grant or deny access for a request.&lt;/p&gt;

&lt;p&gt;What is important to know at this stage is that that an extra step is introduced when AWS Org is enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ycEa29Tw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxcvoaxjpyyuckl0ezz2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ycEa29Tw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxcvoaxjpyyuckl0ezz2.png" alt="Figure 6 - IAM Policy evaluation flow" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where before, only Resource and Identity based policies (and permission boundaries but I am leaving these out right now for the sake of simplicity) were at work determining if a request for access was to be allowed or denied. With the introduction of SCPs, they will also weigh in if a request will be allowed or denied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CsOGsDVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msaffzgw7rwgsmpvl1u6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CsOGsDVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msaffzgw7rwgsmpvl1u6.png" alt="Figure 7 - A deny at the SCP level will deny the entire request" width="800" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tagging policies
&lt;/h2&gt;

&lt;p&gt;The last component I want to highlight within AWS Organizations is tagging policies. These policies can enforce the way resources can be tagged. &lt;br&gt;
*&lt;em&gt;They however do not enforce a specific tag being used. *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "tags": {
        "deparment": {
            "tag_key": {
                "@@assign": "Department"
            },
            "tag_value": {
                "@@assign": [
                    "finance",
                    "sales",
                    "security"
                ]
            },
            "enforced_for": {
                "@@assign": [
                    "ec2:instance",
                    "kms:*"
                ]
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As an example, this policy defines that if the tag “Department” is used, it must have one of the three values defined in &lt;code&gt;“tag_value”&lt;/code&gt;. Furthermore, this enforcement is then only done on &lt;code&gt;ec2:instance&lt;/code&gt; and all kms resources.&lt;/p&gt;

&lt;p&gt;It’s important to fully understand the goal of tagging policies. They are designed to be used for compliance reasons. Using the Management console or the AWS CLI, you can generate a report of non-compliant resources to allow for correction later. &lt;/p&gt;

&lt;p&gt;Also, not all resources can be tagged. &lt;code&gt;“ec2:*”&lt;/code&gt;for instance is not supported and specific resources must be defined. A complete list can be found &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_supported-resources-enforcement.html"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt; &lt;br&gt;
Enabling AWS Org will also enable other services and enable centralised management functionally of existing services. A sample of services which are important to AWS Org or relevant to this document are the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  Centralised billing - Free
&lt;/h3&gt;

&lt;p&gt;Enabling AWS Org instantly turns the account that created it into the “master billing account” (MBA), also known as the Root account. What this means is any account in the AWS Org, including itself, reports its AWS service usage to the MBA from where a single billing dashboard will be available, and a single bill will be generated and paid out to AWS. The consolidated billing dashboard is free to use and a great way to analyse costs across the accounts in the AWS Org by service, account or any tagging enabled for billing purposes. &lt;/p&gt;

&lt;h3&gt;
  
  
  AWS IAM Identity Center (successor to AWS SSO) – Free
&lt;/h3&gt;

&lt;p&gt;This service used has been recently renamed from AWS Single Sign-On (SSO) which is why the name right now is a mouth full as both are being used in this transition phase. &lt;/p&gt;

&lt;p&gt;Identity Center enables the organization to centrally manage and control the AWS Management Console and API access for Users and Groups across all AWS Accounts within the Organization. This is done by using either the built-in Users and Groups functionality of IAM-IC or by connecting to a third-party Identity provider (IdP) like Okta or AzureAD.&lt;/p&gt;

&lt;p&gt;We will go in further detail in the next chapter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise support – Paid
&lt;/h3&gt;

&lt;p&gt;Standard AWS accounts have three tiers of support. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic, which allows only for account, billing and business support cases. &lt;/li&gt;
&lt;li&gt;Developer, which opens up the capability of logging technical support cases.&lt;/li&gt;
&lt;li&gt;Business, which adds P1 and P2 urgency to support cases and give quicker support. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With an Organisation, a fourth tier opens; Enterprise support. (Which itself split up in two tiers. Enterprise On-Ramp and Enterprise.)&lt;/p&gt;

&lt;p&gt;The first three tiers of support were set on an account level. Enterprise support is set at the organizational level and allows for every single account to have the highest grade of support available. &lt;br&gt;
 &lt;/p&gt;

&lt;h2&gt;
  
  
  AWS IAM Identity Center
&lt;/h2&gt;

&lt;p&gt;“AWS IAM Identity Center (successor to AWS SSO)” is the single sign-on solution for AWS accounts joined together within an AWS Org. Identity Center gives a centralised location to manage users and group, their access to AWS accounts within the organization and what rights they have within those accounts using Permission sets. By default, the root account of the AWS Org is the administrator of Identity Center but this can be delegated to, for instance, a dedicated IAM or Security account. This will prevent to many users accessing the, arguably, most important account within the AWS Org. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QkJjMkia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjr2q4p6tqlimim7nkn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QkJjMkia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjr2q4p6tqlimim7nkn7.png" alt="Figure 8 - Visual representation of the interaction between Users/Groups, Permissionsets and AWS Accounts" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A permission set is a structure that contains one or more of the following IAM policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Managed Policies&lt;/li&gt;
&lt;li&gt;Customer managed policies&lt;/li&gt;
&lt;li&gt;Inline Policy, so defined directly inside the permission set&lt;/li&gt;
&lt;li&gt;Optionally, a permissions boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a Permission set is provisioned in a member account, an IAM role is created with policies that correspond to the specific rights defined within the permission set. E.g., an attached policy or an inline policy. These IAM roles (and policies) are protected entities that cannot be modified from the member account.&lt;/p&gt;

&lt;p&gt;When a user logs in via Identity Center, they can then assume the IAM role in the member account to work within this account within the boundaries defined in the IAM role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xYWoyITy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofore8sr0cfjik0iruvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xYWoyITy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ofore8sr0cfjik0iruvh.png" alt="Figure 9 - Simplified overview user access via AWS IAM Identity Center" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The power of AWS IAM Identity Center lies in its capability of integrating with Security Assertion Markup Language (SAML) 2.0 compliant Identity providers (IdP). Most organisations already use an identity provider to manage their users with providers like Okta, Active Directory Federation Services (ADFS) or Ping among others. &lt;/p&gt;

&lt;p&gt;Going one step further, by using System for Cross-Domain Identity Management (SCIM), users and groups can be synchronised automatically from the SAML IdP into IAM-IC.&lt;/p&gt;

&lt;p&gt;The result of this allows organisations to setup a single sign-on environment where users can access AWS with the same credentials they use for their workstation, email, or other work environments. Often without having to even fill in their credentials again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attribute based access control
&lt;/h3&gt;

&lt;p&gt;Identity Center also introduces a feature that is not enabled by default and slightly hidden away but allows organisations to manage user access in an entirely new way by basing access on enterprise specific parameters, which is called Attribute-based access control (ABAC). &lt;/p&gt;

&lt;p&gt;In a simple Azure AD SAML &amp;amp; SCIM setup, only these user attributes are synchronised:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GrahMYgh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6shm37jjc05ryjfec5ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GrahMYgh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6shm37jjc05ryjfec5ah.png" alt="Image description" width="800" height="221"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/attributemappingsconcept.html"&gt;https://docs.aws.amazon.com/singlesignon/latest/userguide/attributemappingsconcept.html&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;These attributes are the ones minimally required to perform successful logins into AWS. &lt;br&gt;
What ABAC allows for is to sync more attributes from AAD into Identity Center. &lt;/p&gt;

&lt;p&gt;Examples of these are department and cost centre. Or office location and function title. &lt;/p&gt;

&lt;p&gt;By passing these attributes into AWS and linking them in ABAC, direct references to these can be made by Identity Provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y-o__Ko9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2crhu56o0kza9lf60k4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y-o__Ko9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2crhu56o0kza9lf60k4.png" alt="Figure 10 - Example view of Attribute-based Access control" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These attributes, as the name of the service implies, can then be used to help dictate access for users within AWS. &lt;/p&gt;

&lt;p&gt;Now, let’s bring all these components together and use them in tandem to provide easy, secure, and flexible access into AWS. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/ndewijer/scaling-identity-access-management-from-startups-to-enterprises-with-aws-solutions-part-2-52m9"&gt;Read part 2 of this blog.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>awsorganizations</category>
      <category>iamidentitycenter</category>
      <category>accessmanagement</category>
      <category>scalability</category>
    </item>
    <item>
      <title>An AWS Community Builders holiday story</title>
      <dc:creator>ndewijer</dc:creator>
      <pubDate>Tue, 20 Dec 2022 11:00:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/an-aws-community-builders-holiday-story-5g02</link>
      <guid>https://dev.to/aws-builders/an-aws-community-builders-holiday-story-5g02</guid>
      <description>&lt;p&gt;Hi all!&lt;/p&gt;

&lt;p&gt;I love the initiative that the 'HotChowSprings' crew thought up. There has been a lot of love been given to the community from the community leaders which immediately moves into the first question!  &lt;/p&gt;

&lt;h3&gt;
  
  
  What surprises you most about the community builders program?
&lt;/h3&gt;

&lt;p&gt;I've been surprised by the level of interaction between the community leaders and the community builders. But also, and this activity is just a public example, of the community builders giving back to the program. &lt;/p&gt;

&lt;p&gt;I love the Slack community. The help we give each other from technical and mindset, to education and training. It's spectacular. &lt;/p&gt;

&lt;h3&gt;
  
  
  What’s your background and your experience with AWS?
&lt;/h3&gt;

&lt;p&gt;I've been working with AWS for years. I started together with my then company going from datacenter to AWS. &lt;/p&gt;

&lt;p&gt;I started as a Windows engineer when the cloud project started. But by stepping up and proposing, designing the method of migrating Splunk into AWS using IaC. The company gave me the trust to implementing it and I succeeded without downtime and under time/budget. &lt;/p&gt;

&lt;p&gt;Over the years, I've designed an implemented more and more workloads on AWS. Moved companies and now I do architecture for a living!&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s the biggest benefit you see from the program?
&lt;/h3&gt;

&lt;p&gt;The biggest benefit I feel is the connection with like-minded people all over the world. You need to apply to get into the Community Builders. And everyone that made it in has put in a good amount effort to bring AWS to the world. &lt;/p&gt;

&lt;p&gt;By speaking with other Community Builders, I get inspiration in my own work. And I've helped people out with technical and architectural advice. &lt;/p&gt;

&lt;h3&gt;
  
  
  What’s the next swag item that you would like to get?
&lt;/h3&gt;

&lt;p&gt;I am such a hoodie-hoarder. I wear them every day. My 2020 hoodie has been worn down that my wife is complaining. &lt;/p&gt;

&lt;p&gt;This year, at Re:Invent I was able to bribe my colleague for his hoodie so I could bring back two and could switch them.&lt;/p&gt;

&lt;p&gt;So, in case it wasn't obvious, I'd like a lanyard... No, a hoodie! CB Hoodie! GIEV! ;)&lt;/p&gt;

&lt;h3&gt;
  
  
  What are you eating for dinner today? Share the recipe!
&lt;/h3&gt;

&lt;p&gt;Tonight I'm making Chinese Mushroom Noodle Soup!&lt;/p&gt;

&lt;p&gt;The base recipe is here:&lt;br&gt;
&lt;a href="https://www.errenskitchen.com/chinese-mushroom-noodle-soup/"&gt;https://www.errenskitchen.com/chinese-mushroom-noodle-soup/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm adding a bit of spring onion and pak choi to it for some more veggies!&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there anything else you would like to say about the community builders program in 2022?
&lt;/h3&gt;

&lt;p&gt;Thank you for existing. Your public and private presentations and interactions are amazing, the events are great and the community is awesome. I love being part of it. Giving us this platform on dev.to is great, even though I haven't used it much. (a blog is coming though!) If I were to give any comment, especially for our use of dev.to, it would be to inspire for quality over quantity. &lt;/p&gt;

</description>
      <category>cbchristmas2022</category>
    </item>
  </channel>
</rss>
