<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://matsev.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://matsev.github.io/" rel="alternate" type="text/html" /><updated>2024-07-09T21:14:28+02:00</updated><id>https://matsev.github.io/feed.xml</id><title type="html">Random Code Ramblings</title><subtitle>Mattias Severson&apos;s website. This is where I present my profile, list my public events, publish my blog posts and list references to my other profiles online.</subtitle><author><name>Mattias Severson</name></author><entry><title type="html">Serverless Twilio Dictionary</title><link href="https://matsev.github.io/blog/2020/12/02/serverless-twilio-dictionary/" rel="alternate" type="text/html" title="Serverless Twilio Dictionary" /><published>2020-12-02T00:00:00+01:00</published><updated>2020-12-02T00:00:00+01:00</updated><id>https://matsev.github.io/blog/2020/12/02/serverless-twilio-dictionary</id><content type="html" xml:base="https://matsev.github.io/blog/2020/12/02/serverless-twilio-dictionary/"><![CDATA[<p>I recently started working at <a href="https://www.twilio.com">Twilio</a>, a communications company that enables services like voice, text, chat and video globally through APIs. As part of the onboarding process, Twilions (Twilio employees) are encouraged to design and implement an app or service that uses one or more of Twilio’s service offerings to get familiarized with the company and to “Wear the customer’s shoes” as it is stated in the <a href="https://www.twilio.com/company/values">company values</a>.</p>

<h2 id="tldr">tl;dr</h2>

<p>I have implemented a proof of concept project based on <a href="https://www.twilio.com/docs/runtime/functions">Twilio Functions</a>, <a href="https://www.twilio.com/sync">Twilio Sync</a>, <a href="https://www.twilio.com/phone-numbers">Twilio Phone Numbers</a> and <a href="https://www.twilio.com/docs/runtime/assets">Twilio Assets</a>. The source code is available in my <a href="https://github.com/matsev/serverless-twilio-dictionary">serverless-twilio-dictionary</a> GitHub repo.</p>

<h2 id="infrastructure">Infrastructure</h2>

<p>Given my background as a backend developer and serverless proponent I was pleasantly surprised to learn that Twilio offers serverless hosting options. <a href="https://www.twilio.com/docs/runtime/functions">Twilio Functions</a> is a service similar to AWS Lambda or GCP Cloud Functions, thus capable of handling events so it was an easy pick. Looking further, I decided that my app should have some kind of persistent data storage for retaining data between invocations and <a href="https://www.twilio.com/sync">Twilio Sync</a> provides just that. To be fair, Sync has more features such as client subscriptions notifications when data is updated, but for me it was more important to get the integration working instead of taking full advantage of all its capabilities. <a href="https://www.twilio.com/phone-numbers">Twilio Phone Numbers</a> was used as frontend which enabled SMS communication between mobile phone and the function. Lastly, I implemented a landing page for my app using <a href="https://www.twilio.com/docs/runtime/assets">Twilio Assets</a>.</p>

<p><img src="/assets/images/serverless-twilio-dictionary-architecture.svg" alt="architecture" /></p>

<h2 id="application">Application</h2>

<p>The application is pretty simple. I chose to implement a dictionary with a CRUD (create, read, update and delete) based command interface. The interaction with the Twilio Sync service is handled in the <a href="https://github.com/matsev/serverless-twilio-dictionary/blob/master/assets/dictionary.private.js">assets/dictionary.private.js</a> file, the SMS message handler is located in the <a href="https://github.com/matsev/serverless-twilio-dictionary/blob/master/functions/sms/dictionary.protected.js">functions/sms/dictionary.protected.js</a> and the landing page resources are available in the <a href="https://github.com/matsev/serverless-twilio-dictionary/tree/master/assets">assets</a> folder.</p>

<p>The commands and their syntax that can be used to communicate with the dictionary are explained in the <a href="https://github.com/matsev/serverless-twilio-dictionary#usage">usage</a> guide in the GitHub readme, in addition to on landing page after the app has been deployed.</p>

<h2 id="build-automation">Build Automation</h2>

<p>One important aspect of software development for me is build and deployment automation. Although “point and click configuration” in a web UI may work well in some cases (e.g. small project like this one), I prefer to automate and version control these aspects like any other source code. It is quicker and less error prone to execute a script than to manually read, interpret and execute steps in a runbook. Therefore, I spent some time to implement a <a href="https://github.com/matsev/serverless-twilio-dictionary/blob/master/scripts/setup.sh">setup</a> script as well as a corresponding <a href="https://github.com/matsev/serverless-twilio-dictionary/blob/master/scripts/teardown.sh">teardown</a> script for this project. All infrastructure is provisioned by executing the former and is subsequently decommissioned when executing the latter.</p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://github.com/matsev/serverless-twilio-dictionary">Source code</a> at GitHub</li>
  <li><a href="https://www.twilio.com/phone-numbers">Twilio Phone Numbers</a> for registering and managing virtual phone numbers</li>
  <li><a href="https://www.twilio.com/docs/runtime/functions">Twilio Functions</a> for backend application hosting</li>
  <li><a href="https://www.twilio.com/sync">Twilio Sync</a> for data persistence</li>
  <li><a href="https://www.twilio.com/docs/runtime/assets">Twilio Assets</a> for hosting web sites</li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="serverless" /><category term="cloud" /><category term="serverless" /><category term="Twilio" /><summary type="html"><![CDATA[I recently started working at Twilio, a communications company that enables services like voice, text, chat and video globally through APIs. As part of the onboarding process, Twilions (Twilio employees) are encouraged to design and implement an app or service that uses one or more of Twilio’s service offerings to get familiarized with the company and to “Wear the customer’s shoes” as it is stated in the company values.]]></summary></entry><entry><title type="html">EventBridge and EC2 Spot Instances</title><link href="https://matsev.github.io/blog/2020/10/11/eventbridge-ec2-spot-instances/" rel="alternate" type="text/html" title="EventBridge and EC2 Spot Instances" /><published>2020-10-11T00:00:00+02:00</published><updated>2020-10-11T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2020/10/11/eventbridge-ec2-spot-instances</id><content type="html" xml:base="https://matsev.github.io/blog/2020/10/11/eventbridge-ec2-spot-instances/"><![CDATA[<p>In a recent project, we were using <a href="https://aws.amazon.com/ec2/spot/">AWS EC2 Spot instances</a> as part of our cloud test environment. A good solution which catered for cost savings, but as expected the instances were interrupted from time to time. One limitation was that we did not know <em>why</em> the instance stopped, was it manually terminated, stopped by an application error or was it an EC2 spot interruption? This blog post shows how <a href="https://aws.amazon.com/eventbridge/">Amazon EventBridge</a> can be configured to trigger a simple Lambda function when spot instances are interrupted.</p>

<h2 id="introduction">Introduction</h2>

<p>In case you are not familiar with EventBridge, here is a paragraph copied from its <a href="https://aws.amazon.com/eventbridge/">product page</a>:</p>
<blockquote>
  <p>Amazon EventBridge is a serverless event bus that makes it easy to connect applications together using data from your own applications, integrated Software-as-a-Service (SaaS) applications, and AWS services. EventBridge delivers a stream of real-time data from event sources, such as Zendesk, Datadog, or Pagerduty, and routes that data to targets like AWS Lambda. You can set up routing rules to determine where to send your data to build application architectures that react in real time to all of your data sources.</p>
</blockquote>

<p>As can be seen, EventBridge is a versatile and powerful tool. In this particular example, we will see how it can be used for the specific purpose of detecting EC2 Spot instance interruptions.</p>

<h2 id="infrastructure">Infrastructure</h2>

<p>The two main components of the infrastructure is one <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/create-eventbridge-rule.html">EventBridge Rule</a> and a Lambda function that is triggered when an event is matched by the rule. Using the <a href="https://docs.aws.amazon.com/cdk/latest/guide/home.html">AWS CDK</a>, it is a pretty straight forward configuration:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">cdk</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@aws-cdk/core</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">lambda</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@aws-cdk/aws-lambda</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">logs</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@aws-cdk/aws-logs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">events</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@aws-cdk/aws-events</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">eventsTargets</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@aws-cdk/aws-events-targets</span><span class="dl">'</span>


<span class="k">export</span> <span class="kd">class</span> <span class="nc">SpotInstantceEventDetectorStack</span> <span class="kd">extends</span> <span class="nc">cdk</span><span class="p">.</span><span class="nx">Stack</span> <span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="nx">scope</span><span class="p">:</span> <span class="nx">cdk</span><span class="p">.</span><span class="nx">Construct</span><span class="p">,</span> <span class="nx">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">props</span><span class="p">?:</span> <span class="nx">cdk</span><span class="p">.</span><span class="nx">StackProps</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="p">(</span><span class="nx">scope</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">props</span><span class="p">);</span>

    <span class="c1">// Lambda function that will triggered</span>
    <span class="kd">const</span> <span class="nx">spotInstanceEventLambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">lambda</span><span class="p">.</span><span class="nc">Function</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">SpotInstanceEventLambda</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Handles Spot Instance events</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">runtime</span><span class="p">:</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">Runtime</span><span class="p">.</span><span class="nx">NODEJS_12_X</span><span class="p">,</span>
      <span class="na">code</span><span class="p">:</span> <span class="k">new</span> <span class="nx">lambda</span><span class="p">.</span><span class="nc">AssetCode</span><span class="p">(</span><span class="dl">'</span><span class="s1">lambdas</span><span class="dl">'</span><span class="p">),</span>
      <span class="na">handler</span><span class="p">:</span> <span class="dl">'</span><span class="s1">spot-instance-event-lambda.handler</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">logRetention</span><span class="p">:</span> <span class="nx">logs</span><span class="p">.</span><span class="nx">RetentionDays</span><span class="p">.</span><span class="nx">TWO_WEEKS</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="c1">// Events Rule that will trigger the Lambda when an EC2 interruption event occurs</span>
    <span class="k">new</span> <span class="nx">events</span><span class="p">.</span><span class="nc">Rule</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">SpotInstanceEventRule</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Rule for tracking spot instance interruptions</span><span class="dl">'</span><span class="p">,</span>
      <span class="na">eventPattern</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">source</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">aws.ec2</span><span class="dl">'</span><span class="p">],</span>
        <span class="na">detailType</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">EC2 Spot Instance Interruption Warning</span><span class="dl">'</span><span class="p">],</span>
      <span class="p">},</span>
      <span class="na">targets</span><span class="p">:</span> <span class="p">[</span><span class="k">new</span> <span class="nx">eventsTargets</span><span class="p">.</span><span class="nc">LambdaFunction</span><span class="p">(</span><span class="nx">spotInstanceEventLambda</span><span class="p">)],</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Apart from the EventBridge Rule and the Lambda function, three(*) more AWS resources are generated, one IAM role that is configured with the <code class="language-plaintext highlighter-rouge">AWSLambdaBasicExecutionRole</code> (thus allowing the Lambda to log to CloudWatch), a <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html">Lambda Permission</a> that allows EventBridge to invoke the Lambda and lastly a <code class="language-plaintext highlighter-rouge">CDKMetadata</code> resource that, as the name implies, contains metadata about the CDK.</p>

<p>(*) Actually, with the <code class="language-plaintext highlighter-rouge">logRetention</code> configuration in place there are yet another four resources created. This setting causes the CDK to configure the Lambda CloudWatch log retention policy using a custom resource with another Lambda function, an IAM Role and an IAM Policy.</p>

<h2 id="events-event-filters-and-event-targets">Events, Event Filters and Event Targets</h2>

<p>In the sample above, events are filtered using the <code class="language-plaintext highlighter-rouge">eventPattern</code> configuration part of the event rule. All AWS event have the same <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/aws-events.html">event structure</a> and here we use the <code class="language-plaintext highlighter-rouge">source</code> and <code class="language-plaintext highlighter-rouge">detailType</code> as <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/filtering-examples-structure.html">event pattern</a> to filter out the Spot instance interruption events. Please see the EventBridge user guide for a comprehensive list of <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html">Event Examples from Supported AWS Services</a>. 
Going further the <code class="language-plaintext highlighter-rouge">targets</code> property have been configured with the Lambda function. It should be noted that there are many more <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-targets.html">EventBridge Targets</a> to choose from, such as Kinesis, StepFunctions, API Gateway and so on.</p>

<h2 id="application-code">Application Code</h2>

<p>In its simplest form, the Lambda function logs the important parts of the spot instance interruption events to CloudWatch:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">time</span><span class="p">,</span> <span class="nx">region</span><span class="p">,</span> <span class="na">detail</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">instance-id</span><span class="dl">'</span><span class="p">:</span> <span class="nx">instanceId</span><span class="p">,</span> <span class="dl">'</span><span class="s1">instance-action</span><span class="dl">'</span><span class="p">:</span> <span class="nx">instanceAction</span> <span class="p">}</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">event</span><span class="p">;</span>

  <span class="nx">console</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="nx">time</span><span class="p">,</span> <span class="nx">region</span><span class="p">,</span> <span class="nx">instanceId</span><span class="p">,</span> <span class="nx">instanceAction</span> <span class="p">}));</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Now, how would you like to be notified? What is important for your service? Perhaps logging to CloudWatch like above will suffice? Maybe it makes sense to store the events in DynamoDB? Or perhaps send a Slack notification? Different teams and different projects have different requirements, but this gives you a good starting point for further endeavours.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/matsev/spot-instance-event-detector">Example Project at GitHub</a></li>
  <li><a href="https://aws.amazon.com/cdk/">AWS Cloud Development Kit (CDK)</a></li>
  <li><a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-interruptions.html">Spot Instance interruptions</a></li>
  <li><a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/aws-events.html">AWS Events</a></li>
  <li><a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/filtering-examples-structure.html">Event Patterns</a></li>
  <li><a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-targets.html">EventBridge Targets</a></li>
  <li><a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html">EventBridge Event Examples from Supported AWS Services</a>.</li>
  <li><a href="https://aws.amazon.com/blogs/compute/taking-advantage-of-amazon-ec2-spot-instance-interruption-notices/">Taking Advantage of Amazon EC2 Spot Instance Interruption Notices</a></li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="serverless" /><category term="AWS" /><category term="cdk" /><category term="cloud" /><category term="EventBridge" /><category term="Lambda" /><category term="serverless" /><category term="spot instance" /><summary type="html"><![CDATA[In a recent project, we were using AWS EC2 Spot instances as part of our cloud test environment. A good solution which catered for cost savings, but as expected the instances were interrupted from time to time. One limitation was that we did not know why the instance stopped, was it manually terminated, stopped by an application error or was it an EC2 spot interruption? This blog post shows how Amazon EventBridge can be configured to trigger a simple Lambda function when spot instances are interrupted.]]></summary></entry><entry><title type="html">Automating Dependency Updates</title><link href="https://matsev.github.io/blog/2020/09/01/automating-dependency-updates/" rel="alternate" type="text/html" title="Automating Dependency Updates" /><published>2020-09-01T00:00:00+02:00</published><updated>2020-09-01T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2020/09/01/automating-dependency-updates</id><content type="html" xml:base="https://matsev.github.io/blog/2020/09/01/automating-dependency-updates/"><![CDATA[<p>Updating project dependencies is a task that is often neglected, especially in legacy projects that are “done” and just work without changes. It is easy to understand why, without automation it is tedious work that has to be repeated every so often for all of your projects. This post is an introduction to <a href="https://docs.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically">GitHub’s Dependabot</a> which offloads the repetitive parts of the job to the machines.</p>

<h2 id="motivation">Motivation</h2>

<p>Perhaps the most important reason why you should spend time to keep your third party libraries up to date is that every now and then a new security vulnerability is discovered and your customers, your data and your business may be at stake. By dealing with updates regularly the risk of having a daunting cascade of migrations is mitigated and the workload is distributed over time. Furthermore, with tools such as Dependabot that enables both automation of discovering and execution of dependency version updates there is no excuse why this should be overlooked.</p>

<h2 id="step-by-step">Step by Step</h2>

<p>Updating third party dependencies typically involves the following steps:</p>

<ol>
  <li>Check out the project</li>
  <li>Find out which dependencies that can be updated. Typically, your project management tools can assist, the <a href="https://www.mojohaus.org/versions-maven-plugin/index.html">Versions Maven Plugin</a> is helpful for Java Maven projects, <a href="https://classic.yarnpkg.com/en/docs/cli/upgrade">yarn upgrade</a> and <a href="https://classic.yarnpkg.com/en/docs/cli/upgrade-interactive">yarn upgrade-interactive</a> for JavaScript Yarn to name a few examples</li>
  <li>Fix API incompatibility issues that the update incurred</li>
  <li>Submit a pull request</li>
  <li>Rebuild and retest the project to verify that there has been no regression</li>
  <li>Deploy</li>
  <li>Repeat</li>
</ol>

<p>Assuming that you already use a build server for continuous integration that takes care of step <code class="language-plaintext highlighter-rouge">5</code> and possibly also step <code class="language-plaintext highlighter-rouge">6</code> if you also have continuous deployment in place. With Dependabot configured in the project, steps <code class="language-plaintext highlighter-rouge">1</code>, <code class="language-plaintext highlighter-rouge">2</code>, <code class="language-plaintext highlighter-rouge">4</code> and <code class="language-plaintext highlighter-rouge">7</code> are also managed automatically. What about step <code class="language-plaintext highlighter-rouge">3</code>, API incapability issues? It turns out, this is not always a problem. If your dependencies have adopted <a href="https://semver.org">semantic versioning</a>, then you will have a indicator if the update will succeed or fail without code changes. If the new version is just a minor or patch release it is likely to pass. Even if it is a new major version, your code may still work if you are not using the parts of the API with the breaking changes.</p>

<h2 id="dependabot-example">Dependabot Example</h2>

<p>This example uses <a href="https://docs.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically">GitHub’s Dependabot</a> for dependency checks for two reasons. Firstly, it is provided out of the box for GitHub projects. Meaning, you don’t have to deploy or manage any infrastructure or build server to make use of it. Secondly, it does not require much configuration to get started:</p>

<ol>
  <li>Create a <a href="https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file">dependabot.yml</a> file in the <code class="language-plaintext highlighter-rouge">.github</code> folder in your project root</li>
  <li>specify <code class="language-plaintext highlighter-rouge">version: 2</code> and an array of <code class="language-plaintext highlighter-rouge">updates</code> (one configuration for each package manager that you would like to check)</li>
  <li>Each update have three required options (there are more, please <a href="https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates">configuration options</a> for an exhaustive list)</li>
</ol>

<table>
  <thead>
    <tr>
      <th>option</th>
      <th>description</th>
      <th>example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">package-ecosystem</code></td>
      <td>Package manager to use</td>
      <td><code class="language-plaintext highlighter-rouge">gradle</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">directory</code></td>
      <td>Location of the package manifest (in relation to the repository root)</td>
      <td><code class="language-plaintext highlighter-rouge">/</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">schedule.interval</code></td>
      <td>How often to check for updates</td>
      <td><code class="language-plaintext highlighter-rouge">"weekly"</code></td>
    </tr>
  </tbody>
</table>

<p>Thus, a <code class="language-plaintext highlighter-rouge">dependabot.yml</code> file for a Docker project could look something like:</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">updates</span><span class="pi">:</span>

  <span class="c1"># Enable version updates for Docker</span>
  <span class="pi">-</span> <span class="na">package-ecosystem</span><span class="pi">:</span> <span class="s2">"</span><span class="s">docker"</span>
    <span class="c1"># Look for a `Dockerfile` in the `root` directory</span>
    <span class="na">directory</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/"</span>
    <span class="c1"># Check for updates once a week</span>
    <span class="na">schedule</span><span class="pi">:</span>
      <span class="na">interval</span><span class="pi">:</span> <span class="s2">"</span><span class="s">weekly"</span>
</code></pre></div></div>

<p>There is nothing that prevents Dependabot from checking two (or more) different build systems. For example, this site is built using bundler and GitHub Actions. The corresponding <code class="language-plaintext highlighter-rouge">dependabot.yml</code> file is in the GitHub <a href="https://github.com/matsev/matsev.github.io/blob/dev/.github/dependabot.yml">project repo</a> and there is also an example of a <a href="https://github.com/matsev/matsev.github.io/pull/2">pull request</a> generated by dependabot.</p>

<h2 id="alternatives">Alternatives</h2>

<p>Two other tools similar to Dependabot are <a href="https://snyk.io">Snyk</a> and <a href="https://renovate.whitesourcesoftware.com">WhiteSource Renovate</a></p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://docs.github.com/en/github/administering-a-repository/about-github-dependabot-version-updates#supported-repositories-and-ecosystems">Dependabot supported repositories and ecosystems</a></li>
  <li><a href="https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates">Configuration options for dependency updates</a></li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="build automation" /><category term="CI/CD" /><category term="security" /><category term="build automation" /><category term="CI/CD" /><category term="Dependabot" /><category term="GitHub" /><category term="security" /><summary type="html"><![CDATA[Updating project dependencies is a task that is often neglected, especially in legacy projects that are “done” and just work without changes. It is easy to understand why, without automation it is tedious work that has to be repeated every so often for all of your projects. This post is an introduction to GitHub’s Dependabot which offloads the repetitive parts of the job to the machines.]]></summary></entry><entry><title type="html">AWS CDK MFA</title><link href="https://matsev.github.io/blog/2020/03/23/aws-cdk-mfa/" rel="alternate" type="text/html" title="AWS CDK MFA" /><published>2020-03-23T00:00:00+01:00</published><updated>2020-03-23T00:00:00+01:00</updated><id>https://matsev.github.io/blog/2020/03/23/aws-cdk-mfa</id><content type="html" xml:base="https://matsev.github.io/blog/2020/03/23/aws-cdk-mfa/"><![CDATA[<p>The <a href="https://aws.amazon.com/cdk/">AWS CDK (Cloud Development Kit)</a> is a welcome contribution to the “Infrastructure as Code” family. It is a development framework that allows you to configure AWS resources using programming languages like TypeScript, JavaScript, Java, Python and C#. In this post, I will present how you can improve the security of your AWS account by enabling MFA (Multi Factor Authentication) when you are working with the AWS CDK.</p>

<h2 id="update">Update</h2>

<p>The AWS CDK version <a href="https://github.com/aws/aws-cdk/releases/tag/v1.60.0">v1.60.0</a> was released August 19, 2020 and it partly solves some of problems mentioned in this post. One drawback with this version is that the MFA credentials are not cached, i.e. they need to entered every time a CDK CLI command is invoked. A new feature request has been created <a href="https://github.com/aws/aws-cdk/issues/9855">#9855 - [cli] Cache mfa credentials</a> to address this issue. Meanwhile, keep on reading if you are interested in a MFA solution for the CDK CLI that provides caching as well.</p>

<h2 id="background">Background</h2>

<p>I recently had the opportunity to try the AWS CDK in a real project. The <a href="https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html">getting started guide</a> provided a good introduction and soon it was time for the first deployment. The intended deployment account was configured to use MFA for the AWS CLI (see my previous blog <a href="/blog/2017/11/22/aws-cli-mfa/">AWS CLI MFA</a>) which made deployment more complicated. That said, the paragraph about Specifying Your Credentials and Region using AWS CLI profiles seemed to provide a solution. I thought I could just pass my AWS CLI configured MFA profile and it would “just work” (spoiler: it didn’t):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npx cdk deploy <span class="nt">--profile</span> my-project-mfa
Need to perform AWS calls <span class="k">for </span>account 123456789012, but no credentials found. Tried: default credentials.
</code></pre></div></div>

<p>Regrettably, it turns out that the AWS CDK does not currently support MFA natively. There are a couple of open issues in the AWS CDK GitHub repository that addresses this concern (<a href="https://github.com/aws/aws-cdk/issues/1248">#1248</a> and <a href="https://github.com/aws/aws-cdk/issues/1656">#1656</a>). However they are more than a year old, so rather than waiting for them to be resolved, I thought I’d better look for other solutions. Preferably a solution that could leverage my MFA configuration referenced earlier. In the end, I found the <a href="https://www.npmjs.com/package/cdk-multi-profile-plugin">cdk-multi-profile-plugin</a> npm package and I am quite happy with the result, especially since I was already using npm as a package manager for my CDK project.</p>

<h2 id="prerequisites">Prerequisites</h2>

<ul>
  <li>
    <p>Please spend some time to read up on how MFA can be configured for AWS CLI access if you are not familiar with the topic. For example, read my <a href="/blog/2017/11/22/aws-cli-mfa/">AWS CLI MFA</a> blog or the AWS CLI user guide about <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-mfa">Using Multi-Factor Authentication</a>.</p>
  </li>
  <li>
    <p>Install the <a href="https://www.npmjs.com/package/cdk-multi-profile-plugin">cdk-multi-profile-plugin</a>:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">$ </span>npm <span class="nb">install </span>cdk-multi-profile-plugin <span class="nt">--save-dev</span>
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="configuration">Configuration</h2>

<p>There are a few different ways to configure the <code>cdk-multi-profile-plugin</code> depending on how it is used. In my team, every developer is responsible for managing his or her own development environment. As a consequence, the developers also create and manage AWS CLI profiles independently of the other team members. There are a few things required in order for this to work:</p>

<h3 id="cdkjson">cdk.json</h3>

<p>Add the plugin to the <a href="https://github.com/aws/aws-cdk/blob/master/packages/aws-cdk/README.md#configuration">cdk.json</a> file, i.e. the CDK configuration file in the project root (this file was generated when by the <code class="language-plaintext highlighter-rouge">cdk init</code> command, see the <a href="https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html">Getting Started Guide</a>):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"app"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx ts-node bin/YOURAPP.ts"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"plugin"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"cdk-multi-profile-plugin"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Make sure to replace the <code class="language-plaintext highlighter-rouge">bin/YOURAPP.ts</code> with the correct value for your CDK project before adding this file to version control.</p>

<h3 id="awsconfig">~/.aws/config</h3>

<p>Next, imagine that you have the following AWS CLI profiles in your <code class="language-plaintext highlighter-rouge">~/.aws/config</code> file (see <a href="/blog/2017/11/22/aws-cli-mfa/#aws-cli-profiles">previous blog post</a> for details):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>profile my-project-mfa]
source_profile <span class="o">=</span> my-project
role_arn <span class="o">=</span> arn:aws:iam::123456789012:role/AdminMFARole
mfa_serial <span class="o">=</span> arn:aws:iam::123456789012:mfa/my-user-name
</code></pre></div></div>

<p>Explanation:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">source_profile</code> name of the profile configured in the <code class="language-plaintext highlighter-rouge">~/.aws/config</code> file, i.e. the profile that has the <code class="language-plaintext highlighter-rouge">aws_access_key_id</code> and <code class="language-plaintext highlighter-rouge">aws_secret_access_key</code></li>
  <li><code class="language-plaintext highlighter-rouge">role_arn</code> ARN of role to be assumed when accessing resources in the account (either using the AWS CLI or in this case using the CDK)</li>
  <li><code class="language-plaintext highlighter-rouge">mfa_serial</code> ARN of your configured MFA device</li>
</ul>

<p>The values are specific both for each developer and for each project, make sure to update them accordingly.</p>

<h3 id="cdkmultiprofilepluginjson">cdkmultiprofileplugin.json</h3>

<p>Lastly, we must associate each AWS account in the <a href="https://docs.aws.amazon.com/cdk/latest/guide/environments.html">CDK Environment</a> with a corresponding AWS CLI profile that we can use to access that environment. For this reason, each developer creates his or her own <code class="language-plaintext highlighter-rouge">cdkmultiprofileplugin.json</code> configuration file and adds it to the project root, e.g.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"awsProfiles"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"123456789012"</span><span class="p">:</span><span class="w"> </span><span class="s2">"my-project-mfa"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The number <code class="language-plaintext highlighter-rouge">123456789012</code> maps to the AWS Account ID used in the CDK Environment configuration. The value <code class="language-plaintext highlighter-rouge">my-project-mfa</code> is the name of the AWS CLI profile for the same account as outlined in the <a href="#awsconfig">~/.aws/config</a> paragraph. You can add more account / CLI profile pairs if the CDK Environment has multiple deployment accounts.</p>

<p>Do not add this file to version control unless you have agreed to use common CLI profile names. If this is the case, keep in mind that there are other configuration options as well. The plugin <a href="https://github.com/hupe1980/cdk-multi-profile-plugin/blob/master/cdk-multi-profile-plugin/README.md">README</a> presents all options so that you can choose a suitable solution for your project.</p>

<h2 id="usage">Usage</h2>

<p>Now that we have all pieces in place, we can build and deploy the stack!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm run build <span class="o">&amp;&amp;</span> npx cdk deploy

🚀  Using profile my-project-mfa <span class="k">for </span>account 123456789012 <span class="k">in </span>mode ForReading
  
? MFA token <span class="k">for </span>arn:aws:iam::123456789012:mfa/my-user-name: <span class="o">[</span>enters MFA token]

This deployment will make potentially sensitive changes according to your current security approval level <span class="o">(</span><span class="nt">--require-approval</span> broadening<span class="o">)</span><span class="nb">.</span>
Please confirm you intend to make the following modifications:

<span class="o">[</span>Presents stack changes]

Do you wish to deploy these changes <span class="o">(</span>y/n<span class="o">)</span>?  y

TestStack: deploying...

 🚀  Using profile my-project-mfa <span class="k">for </span>account 123456789012 <span class="k">in </span>mode ForWriting

TestStack: creating CloudFormation changeset...

<span class="o">[</span>CloudFormation stack events]

✅  TestStack
</code></pre></div></div>

<h2 id="dependencies">Dependencies</h2>

<ul>
  <li><a href="https://www.npmjs.com/package/aws-cdk/v/1.28.0">aws-cdk: 1.28.0</a></li>
  <li><a href="https://www.npmjs.com/package/cdk-multi-profile-plugin/v/1.1.2">cdk-multi-profile-plugin: 1.1.2</a></li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="JavaScript" /><category term="security" /><category term="AWS" /><category term="AWS CDK" /><category term="cloud" /><category term="JavaScript" /><category term="MFA" /><category term="security" /><summary type="html"><![CDATA[The AWS CDK (Cloud Development Kit) is a welcome contribution to the “Infrastructure as Code” family. It is a development framework that allows you to configure AWS resources using programming languages like TypeScript, JavaScript, Java, Python and C#. In this post, I will present how you can improve the security of your AWS account by enabling MFA (Multi Factor Authentication) when you are working with the AWS CDK.]]></summary></entry><entry><title type="html">AWS Elasticsearch JavaScript Client</title><link href="https://matsev.github.io/blog/2018/09/11/aws-elasticsearch-javascript-client/" rel="alternate" type="text/html" title="AWS Elasticsearch JavaScript Client" /><published>2018-09-11T00:00:00+02:00</published><updated>2018-09-11T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2018/09/11/aws-elasticsearch-javascript-client</id><content type="html" xml:base="https://matsev.github.io/blog/2018/09/11/aws-elasticsearch-javascript-client/"><![CDATA[<p>I have spent some time working with the <a href="https://aws.amazon.com/elasticsearch-service/">AWS Elasticsearch Service</a> lately. Regrettably, I found the threshold before being productive was higher than I anticipated. One of my obstacles was to get an AWS Elasticsearch JavaScript client working inside an AWS Lambda function, so I thought I’d better make a note of my solution in case I run into a similar problem in the future.</p>

<h2 id="elasticsearch-iam-policy-document">Elasticsearch IAM Policy Document</h2>

<p>Before looking at the client implementation, we need to make sure that it is allowed to access the Elasticsearch domain. As always, this requires that the client is associated with an IAM Policy Document. Adhering to the AWS guideline of <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege">principle of least privileges</a> the policy is as strict as possible.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"Version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2012-10-17"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Statement"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"Effect"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Allow"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es:ESHttp*"</span><span class="p">,</span><span class="w"> 
        </span><span class="nl">"Resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"arn:aws:es:eu-west-1:111122223333:my-domain/*"</span><span class="w">
    </span><span class="p">}]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">*</code> character at the end of the <code class="language-plaintext highlighter-rouge">es:ESHttp*</code> value implies that all HTTP methods are allowed. You may choose to lock down the policy even further. One example is to use <code class="language-plaintext highlighter-rouge">"es:ESHttpGet"</code> for just permitting reading data from Elasticsearch. Another example is <code class="language-plaintext highlighter-rouge">["es:ESHttpPost", "es:ESHttpPut"]</code> for clients that only add data to the domain. Finally, the <code class="language-plaintext highlighter-rouge">Resource</code> property tells us that the policy statement only affects the Elasticsearch domain with the specified ARN.</p>

<h2 id="elasticsearch-client">Elasticsearch Client</h2>

<p>My first naive attempt was to use a HTTP client to make requests to the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docs.html">Elasticsearch HTTP API</a> of my domain. It failed misearably, AWS requires that HTTP requests are signed with <a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html">Signature Version 4</a> to be valid. The AWS SDK handles this internally so usually you do not need to bother. Realizing that, I took a closer look at what functionality the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ES.html">ES</a> class in the AWS JavaScript SDK offers. It does indeed provide an Elasticsearch API, but it is all about domain configuration, management and it does not provide any client features. Next, when I studied the <a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/what-is-amazon-elasticsearch-service.html">AWS Elasticsearch developer guide</a>, I found an <a href="https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-indexing-programmatic.html#es-indexing-programmatic-node">JavaScript client snippet</a>. It had some limitations in my opinion (it uses global variables for request configuration and response handling just logs HTTP status code and response body). For this reason, I chose to rewrite it to a more generic <code class="language-plaintext highlighter-rouge">elasticsearch-client.js</code> file:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
    
<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">AWS</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">aws-sdk</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="p">{</span> <span class="nx">AWS_REGION</span><span class="p">,</span> <span class="nx">ELASTICSEARCH_DOMAIN</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">endpoint</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nc">Endpoint</span><span class="p">(</span><span class="nx">ELASTICSEARCH_DOMAIN</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">httpClient</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nc">HttpClient</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">credentials</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nc">EnvironmentCredentials</span><span class="p">(</span><span class="dl">'</span><span class="s1">AWS</span><span class="dl">'</span><span class="p">);</span>

<span class="cm">/**
 * Sends a request to Elasticsearch
 *
 * @param {string} httpMethod - The HTTP method, e.g. 'GET', 'PUT', 'DELETE', etc
 * @param {string} requestPath - The HTTP path (relative to the Elasticsearch domain), e.g. '.kibana'
 * @param {Object} [payload] - An optional JavaScript object that will be serialized to the HTTP request body
 * @returns {Promise} Promise - object with the result of the HTTP response
 */</span>
<span class="kd">function</span> <span class="nf">sendRequest</span><span class="p">({</span> <span class="nx">httpMethod</span><span class="p">,</span> <span class="nx">requestPath</span><span class="p">,</span> <span class="nx">payload</span> <span class="p">})</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nc">HttpRequest</span><span class="p">(</span><span class="nx">endpoint</span><span class="p">,</span> <span class="nx">AWS_REGION</span><span class="p">);</span>

    <span class="nx">request</span><span class="p">.</span><span class="nx">method</span> <span class="o">=</span> <span class="nx">httpMethod</span><span class="p">;</span>
    <span class="nx">request</span><span class="p">.</span><span class="nx">path</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">requestPath</span><span class="p">);</span>
    <span class="nx">request</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">;</span>
    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">[</span><span class="dl">'</span><span class="s1">Host</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">ELASTICSEARCH_DOMAIN</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">signer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AWS</span><span class="p">.</span><span class="nx">Signers</span><span class="p">.</span><span class="nc">V4</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="dl">'</span><span class="s1">es</span><span class="dl">'</span><span class="p">);</span>
    <span class="nx">signer</span><span class="p">.</span><span class="nf">addAuthorization</span><span class="p">(</span><span class="nx">credentials</span><span class="p">,</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">());</span>

    <span class="k">return</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">httpClient</span><span class="p">.</span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span>
            <span class="nx">response</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="kd">const</span> <span class="p">{</span> <span class="nx">statusCode</span><span class="p">,</span> <span class="nx">statusMessage</span><span class="p">,</span> <span class="nx">headers</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">response</span><span class="p">;</span>
                <span class="kd">let</span> <span class="nx">body</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
                <span class="nx">response</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">data</span><span class="dl">'</span><span class="p">,</span> <span class="nx">chunk</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="nx">body</span> <span class="o">+=</span> <span class="nx">chunk</span><span class="p">;</span>
                <span class="p">});</span>
                <span class="nx">response</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">end</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
                    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{</span>
                        <span class="nx">statusCode</span><span class="p">,</span>
                        <span class="nx">statusMessage</span><span class="p">,</span>
                        <span class="nx">headers</span>
                    <span class="p">};</span>
                    <span class="k">if </span><span class="p">(</span><span class="nx">body</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">data</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">body</span><span class="p">);</span>
                    <span class="p">}</span>
                    <span class="nf">resolve</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
                <span class="p">});</span>
            <span class="p">},</span>
            <span class="nx">err</span> <span class="o">=&gt;</span> <span class="p">{</span>
                <span class="nf">reject</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
            <span class="p">});</span>
    <span class="p">});</span>
<span class="p">}</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">sendRequest</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="example-usage">Example Usage</h2>

<p>The above implementation enables you to implement all methods in the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.2/docs.html">Elasticsearch HTTP API</a>. The only missing part is an environment variable called <code class="language-plaintext highlighter-rouge">ELASTICSEARCH_DOMAIN</code> that should have the value of your AWS hosted Elasticsearch domain such as <code class="language-plaintext highlighter-rouge">my-domain-qwertyasdf.eu-west-1.es.amazonaws.com</code>. To create a new Elasticsearch index called <code class="language-plaintext highlighter-rouge">my-index</code> you execute the function call by providing the required parameters in the corresponding <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.2/indices-create-index.html">Create Index API</a>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
    
<span class="kd">const</span> <span class="nx">sendElasticsearchRequest</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./elasticsearch-client</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">httpMethod</span><span class="p">:</span> <span class="dl">'</span><span class="s1">PUT</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">requestPath</span><span class="p">:</span> <span class="dl">'</span><span class="s1">my-index</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">payload</span><span class="p">:</span> <span class="p">{</span>
        <span class="c1">// see link above for details</span>
        <span class="na">settings</span><span class="p">:</span> <span class="p">{</span>
            <span class="na">index</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">number_of_shards</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
                <span class="na">number_of_replicas</span><span class="p">:</span> <span class="mi">2</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span>
<span class="nf">sendElasticsearchRequest</span><span class="p">(</span><span class="nx">params</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">response</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
    <span class="p">});</span>
</code></pre></div></div>

<p>And the result may look something like:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> 
    <span class="nl">statusCode</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span>
    <span class="nx">statusMessage</span><span class="p">:</span> <span class="dl">'</span><span class="s1">OK</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">headers</span><span class="p">:</span> <span class="p">{</span> 
        <span class="nl">date</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Wed, 05 Sep 2018 20:24:24 GMT</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">content-type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json; charset=UTF-8</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">content-length</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">67</span><span class="dl">'</span><span class="p">,</span>
        <span class="nx">connection</span><span class="p">:</span> <span class="dl">'</span><span class="s1">keep-alive</span><span class="dl">'</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">access-control-allow-origin</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span> 
    <span class="p">},</span>
    <span class="nx">body</span><span class="p">:</span> <span class="p">{</span> 
        <span class="nl">acknowledged</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="nx">shards_acknowledged</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="nx">index</span><span class="p">:</span> <span class="dl">'</span><span class="s1">my-index</span><span class="dl">'</span>
    <span class="p">}</span> 
<span class="p">}</span>
</code></pre></div></div>

<h2 id="considerations">Considerations</h2>

<ul>
  <li>The Elasticsearch client above returns a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>. Timeouts and unknown domain URLs result in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject">Promise.reject()</a> whereas successful HTTP request/response results in <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve">Promise.resolve()</a>. The resolved JavaScript object has three or four properties, namely the HTTP <code class="language-plaintext highlighter-rouge">statusCode</code>, the HTTP <code class="language-plaintext highlighter-rouge">statusMessage</code>, the HTTP <code class="language-plaintext highlighter-rouge">headers</code> and <code class="language-plaintext highlighter-rouge">body</code> in case there is a HTTP response body. Consequently, the promise will be resolved successfully by any 4XX client error codes (e.g. 404 - Not Found) and 5XX server errors (e.g. 503 Service Unavailable). Feel free to modify the code to reject the promise on HTTP errors if you prefer such behaviour.</li>
  <li>The client uses the <a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EnvironmentCredentials.html">AWS.EnvironmentCredentials</a> class for obtaining valid credentials since it is being deployed as part of a Lambda function. This is not the only Node.js runtime environment and for this reason this is not the only credential class in the SDK. Please study the <a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html">Setting Credentials in Node.js</a> chapter in the AWS JavaScript developer guide for other alternatives.</li>
  <li>A different approach to connect to an AWS Elasticsearch domain is to use the official <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/api-reference-6-2.html">Elasticsearch JavaScript client</a>. Like my HTTP client attempt, it cannot be used directly since it does not have the AWS Signature Version 4 capability. However, it has a pluggable architecture and there is a community <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/15.x/extensions.html">extension</a> called <code class="language-plaintext highlighter-rouge">http-aws-es</code> that solves this problem. I have not tried this method, but they are both available as npm dependencies. Please check <a href="https://www.npmjs.com/package/elasticsearch&quot;">elasticsearch</a> and <a href="https://www.npmjs.com/package/http-aws-es">http-aws-es</a> for more information.</li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="JavaScript" /><category term="AWS" /><category term="cloud" /><category term="Elasticsearch" /><category term="JavaScript" /><category term="Lambda" /><summary type="html"><![CDATA[Blog post that shows how an AWS Elasticsearch JavaScript Client can be implemented in addition to an example of an AWS Elasticsearch IAM policy implementation.]]></summary></entry><entry><title type="html">AWS Glue Dev Endpoint Deleter</title><link href="https://matsev.github.io/blog/2018/07/03/aws-glue-dev-endpoint-deleter/" rel="alternate" type="text/html" title="AWS Glue Dev Endpoint Deleter" /><published>2018-07-03T00:00:00+02:00</published><updated>2018-07-03T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2018/07/03/aws-glue-dev-endpoint-deleter</id><content type="html" xml:base="https://matsev.github.io/blog/2018/07/03/aws-glue-dev-endpoint-deleter/"><![CDATA[<p>Development of AWS Glue scripts can potentially add unnecessary expenses to your invoice if you are not careful. This blog post shows one way to avoid some of the cost in an automated fashion by using AWS CloudFormation and AWS Lambda.</p>

<h2 id="background">Background</h2>

<p>A while ago, I had the opportunity to explore <a href="https://aws.amazon.com/glue/">AWS Glue</a>, a serverless extract, transform and load (ETL) service from AWS. The AWS Glue service offering also includes an optional developer endpoint, a hosted <a href="https://zeppelin.apache.org/">Apache Zeppelin</a> notebook, that facilitates the development and testing of AWS Glue scripts in an interactive manner. Typically, you only pay for the compute resources consumed while running your ETL job. However, in contrast to the rest of AWS Glue family, usage of developer endpoints are charged hourly, regardless if you actively use them or not. You can see this when launching a dev endpoint from the AWS Console:</p>

<blockquote>
  <p>Billing for a development endpoint is based on the Data Processing Unit (DPU) hours used during the entire time it remains in the READY state. To stop charges, delete the endpoint. To delete, choose the endpoint in the list, and then choose Action, Delete.</p>
</blockquote>

<p>Is this something that we should care about? Each dev endpoint gets 5 DPUs by default (with a minimum of 2 DPUs), and they are currently <a href="https://aws.amazon.com/glue/pricing/">priced at $0.44 per DPU-hour</a> (billed per second, 10 minutes minimum). Put it differently, you pay $2.20 per hour or $52.80 per day for the default configuration (or more than $100 for a weekend if you forget to delete your endpoint before you go home for the weekend which I did). For me, this was expensive enough to motivate an automatic dev endpoint deleter.</p>

<h2 id="outline">Outline</h2>

<p>The solution consists of the following components:</p>

<ul>
  <li>A Lambda function that lists all Glue developer endpoints and subsequently deletes them.</li>
  <li>A CloudWatch Event that triggers the Lambda function at scheduled intervals (typically at the end of each workday).</li>
  <li>A Lambda Permission that allows the CloudWatch Event to invoke the Lambda function.</li>
  <li>An IAM Role that allows the Lambda function to get and delete the Glue developer endpoints.</li>
  <li>A CloudFormation template that comprises all resources. Thus, the stack can be re-used across AWS accounts and AWS regions.</li>
</ul>

<h2 id="solution">Solution</h2>

<p>The entire solution is presented in the CloudFormation template below. By inlining the Lambda source code into the template a single file is enough for both the infrastructure as well as the application logic. I have chosen to declare the cron expression as a parameter. I have scheduled the CloudWatch Events to trigger the Lambda when I leave the office, typically at 5PM. Since the cron expression is given in UTC the actual time will depend on daylight saving. A cron expression of <code class="language-plaintext highlighter-rouge">0 16 * * ? *</code> translates to 5PM CET (Central European Time) in the winter and 6PM CEST (Central European Summer Time) in the summer.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">AWSTemplateFormatVersion</span><span class="pi">:</span> <span class="s">2010-09-09</span>
<span class="na">Description</span><span class="pi">:</span> <span class="s">Stack that deletes all Glue Developer Endpoints in a region</span>

<span class="na">Parameters</span><span class="pi">:</span>

  <span class="na">CronExpression</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">String</span>
    <span class="na">Description</span><span class="pi">:</span> <span class="s">The cron expression for triggering the Glue Dev endpoint deletion (in UTC)</span>
    <span class="na">Default</span><span class="pi">:</span> <span class="s">0 16 * * ? *</span>
    <span class="na">ConstraintDescription</span><span class="pi">:</span> <span class="s">Must be a valid cron expression</span>


<span class="na">Resources</span><span class="pi">:</span>

  <span class="na">DeleteGlueEndpointsLambda</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Lambda::Function</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">Description</span><span class="pi">:</span> <span class="s">A Lambda function that gets and deletes the AWS Glue Dev endpoints</span>
      <span class="na">Handler</span><span class="pi">:</span> <span class="s">index.handler</span>
      <span class="na">Code</span><span class="pi">:</span>
        <span class="na">ZipFile</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">'use strict';</span>

          <span class="s">const AWS = require('aws-sdk');</span>
          <span class="s">const glue = new AWS.Glue({apiVersion: '2017-03-31'});</span>

          <span class="s">function deleteDevEndpoints(endpointNames) {</span>
            <span class="s">console.info('Deleting Glue DevEndpoints:', JSON.stringify(endpointNames));</span>
            <span class="s">const promises = endpointNames</span>
              <span class="s">.map(params =&gt; {</span>
                <span class="s">return glue.deleteDevEndpoint(params).promise()</span>
                  <span class="s">.then(data =&gt; {</span>
                    <span class="s">console.info('Deleted:', JSON.stringify(params));</span>
                    <span class="s">return data;</span>
                  <span class="s">});</span>
              <span class="s">});</span>
            <span class="s">return Promise.all(promises);</span>
          <span class="s">}</span>

          <span class="s">function extractEndPointNames(data) {</span>
            <span class="s">return data.DevEndpoints</span>
              <span class="s">.map(({ EndpointName }) =&gt; ({ EndpointName }));</span>
          <span class="s">}</span>

          <span class="s">exports.handler = (event, context, callback) =&gt; {</span>
            <span class="s">console.info('Event:', JSON.stringify(event));</span>
            <span class="s">glue.getDevEndpoints().promise()</span>
              <span class="s">.then(extractEndPointNames)</span>
              <span class="s">.then(deleteDevEndpoints)</span>
              <span class="s">.then(data =&gt; {</span>
                <span class="s">callback(null, data);</span>
              <span class="s">})</span>
              <span class="s">.catch(callback);</span>
          <span class="s">};</span>
      <span class="na">Role</span><span class="pi">:</span> <span class="kt">!GetAtt</span> <span class="s">LambdaExecutionRole.Arn</span>
      <span class="na">Runtime</span><span class="pi">:</span> <span class="s">nodejs6.10</span>

  <span class="na">TriggerRule</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Events::Rule</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">Description</span><span class="pi">:</span> <span class="s">Trigger for the DeleteGlueEndpointsLambda</span>
      <span class="na">ScheduleExpression</span><span class="pi">:</span> <span class="kt">!Sub</span> <span class="s">cron(${CronExpression})</span>
      <span class="na">Targets</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">Arn</span><span class="pi">:</span> <span class="kt">!GetAtt</span> <span class="s">DeleteGlueEndpointsLambda.Arn</span>
        <span class="na">Id</span><span class="pi">:</span> <span class="s">TriggerId</span>

  <span class="na">InvokeLambdaPermission</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::Lambda::Permission</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">FunctionName</span><span class="pi">:</span> <span class="kt">!GetAtt</span> <span class="s">DeleteGlueEndpointsLambda.Arn</span>
      <span class="na">Action</span><span class="pi">:</span> <span class="s">lambda:InvokeFunction</span>
      <span class="na">Principal</span><span class="pi">:</span> <span class="s">events.amazonaws.com</span>
      <span class="na">SourceArn</span><span class="pi">:</span> <span class="kt">!GetAtt</span> <span class="s">TriggerRule.Arn</span>

  <span class="na">LambdaExecutionRole</span><span class="pi">:</span>
    <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::IAM::Role</span>
    <span class="na">Properties</span><span class="pi">:</span>
      <span class="na">AssumeRolePolicyDocument</span><span class="pi">:</span>
        <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
        <span class="na">Statement</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
            <span class="na">Principal</span><span class="pi">:</span>
              <span class="na">Service</span><span class="pi">:</span> <span class="s">lambda.amazonaws.com</span>
            <span class="na">Action</span><span class="pi">:</span> <span class="s">sts:AssumeRole</span>
      <span class="na">ManagedPolicyArns</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole</span>
      <span class="na">Policies</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">PolicyName</span><span class="pi">:</span> <span class="s">GlueDeleteEndpointPolicy</span>
          <span class="na">PolicyDocument</span><span class="pi">:</span>
            <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
            <span class="na">Statement</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
                <span class="na">Action</span><span class="pi">:</span>
                  <span class="pi">-</span> <span class="s">glue:GetDevEndpoint</span>
                  <span class="pi">-</span> <span class="s">glue:GetDevEndpoints</span>
                  <span class="pi">-</span> <span class="s">glue:DeleteDevEndpoint</span>
                <span class="na">Resource</span><span class="pi">:</span>
                  <span class="pi">-</span> <span class="s1">'</span><span class="s">*'</span>
</code></pre></div></div>

<h2 id="resources">Resources</h2>

<p>The AWS documentation is comprehensive, yet it can be hard to navigate. Here are some links that you may find useful:</p>

<ul>
  <li><a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html">Schedule Expressions for Rules</a></li>
  <li><a href="https://docs.aws.amazon.com/lambda/latest/dg/intro-permission-model.html">AWS Lambda Permissions Model</a></li>
  <li><a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html">CloudFormation Resource Types Reference</a></li>
  <li><a href="https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html">Lambda Function Handler</a></li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="tools" /><category term="AWS" /><category term="AWS Glue" /><category term="cloud" /><category term="CloudFormation" /><category term="Lambda" /><summary type="html"><![CDATA[Blog post that describes how you can decrease the costs associated with AWS Glue developer endpoint by implementing an AWS Lambda function that deletes them on a scheduled interval.]]></summary></entry><entry><title type="html">AWS CLI MFA</title><link href="https://matsev.github.io/blog/2017/11/22/aws-cli-mfa/" rel="alternate" type="text/html" title="AWS CLI MFA" /><published>2017-11-22T00:00:00+01:00</published><updated>2017-11-22T00:00:00+01:00</updated><id>https://matsev.github.io/blog/2017/11/22/aws-cli-mfa</id><content type="html" xml:base="https://matsev.github.io/blog/2017/11/22/aws-cli-mfa/"><![CDATA[<p>AWS CLI MFA, how about that for title? It translates to Amazon Web Services Command Line Interface Multi Factor Authentication when all acronyms are spelled out. If you have enabled MFA for the AWS Console you may know that is fairly <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html">straight forward</a> once you have created your IAM user, however it is a different story to configure MFA for the AWS CLI tool. This blog post will present a solution for this problem based on a CloudFormation Template and AWS CLI profiles.</p>

<h2 id="motivation">Motivation</h2>

<p>If you are using the AWS platform from the command line you have configured your terminal for <a href="http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html">CLI access</a> using an AWS Access Key ID and an AWS Secret Access Key. As a result, those values are saved in the <code class="language-plaintext highlighter-rouge">~/.aws/credentials</code> file, i.e. there is a file in your computer in which the AWS account credentials are stored in plain text. Consequently, if your computer is stolen or hacked, a malicious user can use your credentials and then use the AWS CLI tool to do whatever you are privileged to do in your AWS account. Adding MFA to the CLI access will add a significant hurdle for the intruder since they also need your MFA device in addition to your AWS access keys before he or she can do any harm.</p>

<h2 id="outline">Outline</h2>

<p>Currently, there is not easy way of mandating MFA for AWS CLI users. The trick that will be presented in this blog post is to limit the privileges for users that have not authenticated using MFA and then create a separate role with the desired privileges that requires MFA to be assumed. When the AWS CLI tool user switches to the role, the user is prompted for the TOTP (Time-based One-time Password, e.g. a six digit code that the MFA device presents) before the actual role switch occurs. As a result, the user receives <a href="http://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html">temporary security credentials</a> that are valid for 1 hour. Within that time frame multiple CLI commands can be executed without the need of re-authentication. By utilizing two different <a href="http://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html">CLI profiles</a> the role switch occurs if the user just provided the correct profile. To summarize:</p>

<ul>
  <li><a href="#admin-role">Create an IAM Role</a> with the same privileges as the IAM Group. Make sure that the IAM Role requires MFA.</li>
  <li><a href="#admin-group">Create an IAM Group</a> with admin privileges to which we can add users. Make sure that the IAM Group requires MFA.</li>
  <li><a href="#manage-mfa-policy">Create a policy</a> that allows users to manage their MFA configuration.</li>
  <li><a href="#aws-cli-profiles">Create AWS CLI profiles</a> that handle the role switch and MFA authentication for the user.</li>
</ul>

<h2 id="before-you-start">Before You Start</h2>

<p>I advise that you create a temporary backup user with admin privileges (and verify that it works), <em>before</em> you try the proposed solution. Chances are that you accidentally screw up your own user role or privileges and lock yourself out from the account accordingly. If that happens, you can use the root credentials to restore your access, but it may be easier to have a dedicated temporary user that is deleted once you have completed your IAM configuration. The source code has been published to the <a href="https://github.com/matsev/aws-cli-mfa">aws-cli-mfa repository in my GitHub account</a>. There you will find two role / group pairs, one <code class="language-plaintext highlighter-rouge">AdminMFARole</code>/<code class="language-plaintext highlighter-rouge">AdminMFAGroup</code> with full admin privileges that I discuss in detail below. Similarly, there is one <code class="language-plaintext highlighter-rouge">S3MFARole</code>/<code class="language-plaintext highlighter-rouge">S3MFaGroup</code> that has full access to S3, but is prohibited from using other AWS services.</p>

<h2 id="admin-role">Admin Role</h2>

<p>First, we need to create an <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html">AWS::IAM::Role</a>. The privileges of this role will dictate what the AWS CLI user is allowed to do after the role switch.. In this blog post, the role will have admin privileges, but requires MFA ro be assumed, but you create other roles with more limited privileges according to your needs (the example in the <a href="https://github.com/matsev/aws-cli-mfa">GitHub repository</a> also contains an S3 role). AWS comes with a predefined managed policy <a href="https://console.aws.amazon.com/iam/home#policies/arn:aws:iam::aws:policy/AdministratorAccess">arn:aws:iam::aws:policy/AdministratorAccess</a> that we can use, but we need to add an assume role policy document with a <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Condition">Condition</a> that MFA is present using one of the <a href="http://docs.aws.amazon.com//IAM/latest/UserGuide/reference_policies_condition-keys.html#AvailableKeys">AWS Global and IAM Condition Context Keys</a>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">AdminMFARole</span><span class="pi">:</span>
  <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::IAM::Role</span>
  <span class="na">Properties</span><span class="pi">:</span>
    <span class="na">ManagedPolicyArns</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">arn:aws:iam::aws:policy/AdministratorAccess</span>
    <span class="na">RoleName</span><span class="pi">:</span> <span class="s">AdminMFARole</span>
    <span class="na">AssumeRolePolicyDocument</span><span class="pi">:</span>
      <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
      <span class="na">Statement</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">Sid</span><span class="pi">:</span> <span class="s">AllowAssumeRoleIfMFAIsPresent</span>
          <span class="c1"># see http://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html#cli-roles-mfa</span>
          <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
          <span class="na">Principal</span><span class="pi">:</span>
            <span class="na">AWS</span><span class="pi">:</span> <span class="kt">!Ref</span> <span class="s">AWS::AccountId</span>
          <span class="na">Action</span><span class="pi">:</span> <span class="s">sts:AssumeRole</span>
          <span class="na">Condition</span><span class="pi">:</span>
            <span class="na">Bool</span><span class="pi">:</span>
              <span class="na">aws:MultiFactorAuthPresent</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<h2 id="admin-group">Admin Group</h2>

<p>Next step is to create an <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-group.html">AWS::IAM::Group</a>. You can assign which of your IAM users will have admin access by adding them to or removing them from this group. The group is configured with two policies. The first policy allow users in the group to assume the <code class="language-plaintext highlighter-rouge">AdminMFARole</code> above. Note that this policy should be without MFA requirement. Why? If the user had already been MFA authenticated, there would be no need for the role switch since its sole purpose is to trigger the MFA authentication. The second policy document has the same privileges as the previously mentioned <a href="https://console.aws.amazon.com/iam/home#policies/arn:aws:iam::aws:policy/AdministratorAccess">arn:aws:iam::aws:policy/AdministratorAccess</a>, but it adds MFA requirement. Regrettably, it is not possible to copy an existing policy and just add the MFA condition to it, hence we need to specify a full policy document. This second policy is what gives your AWS Console users administrator privileges if they use MFA as part of the login flow. If you only plan to cater for CLI users, this policy can be omitted.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">AdminMFAGroup</span><span class="pi">:</span>
  <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::IAM::Group</span>
  <span class="na">Properties</span><span class="pi">:</span>
    <span class="na">GroupName</span><span class="pi">:</span> <span class="s">AdminMFAGroup</span>
    <span class="na">Policies</span><span class="pi">:</span>

      <span class="pi">-</span> <span class="na">PolicyName</span><span class="pi">:</span> <span class="s">AllowAssumeAdminMFAPolicy</span>
        <span class="na">PolicyDocument</span><span class="pi">:</span>
          <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
          <span class="na">Statement</span><span class="pi">:</span>
            <span class="na">Sid</span><span class="pi">:</span> <span class="s">AllowUserToAssumeAdminMFARole</span>
            <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
            <span class="na">Action</span><span class="pi">:</span> <span class="s">sts:AssumeRole</span>
            <span class="na">Resource</span><span class="pi">:</span> <span class="kt">!GetAtt</span> <span class="s">AdminMFARole.Arn</span>

      <span class="pi">-</span> <span class="na">PolicyName</span><span class="pi">:</span> <span class="s">AdminMFAPolicy</span>
        <span class="c1"># same privileges as the arn:aws:iam::aws:policy/AdministratorAccess, but with MFA requirement</span>
        <span class="na">PolicyDocument</span><span class="pi">:</span>
          <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
          <span class="na">Statement</span><span class="pi">:</span>
            <span class="na">Sid</span><span class="pi">:</span> <span class="s">AllowAdminUserToDoAnythingIfMFAIsPresent</span>
            <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
            <span class="na">Action</span><span class="pi">:</span> <span class="s1">'</span><span class="s">*'</span>
            <span class="na">Resource</span><span class="pi">:</span> <span class="s1">'</span><span class="s">*'</span>
            <span class="na">Condition</span><span class="pi">:</span>
              <span class="na">Bool</span><span class="pi">:</span>
                <span class="na">aws:MultiFactorAuthPresent</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<h2 id="manage-mfa-policy">Manage MFA Policy</h2>

<p>The third step enables MFA self service for all users in the <code class="language-plaintext highlighter-rouge">AdminMFAGroup</code>. Basically, this policy allows the users to create their own MFA configuration without enforcing MFA in the first place, both using the AWS CLI as well as the AWS Console.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">ManageMFAPolicy</span><span class="pi">:</span>
  <span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::IAM::ManagedPolicy</span>
  <span class="na">Properties</span><span class="pi">:</span>
    <span class="na">Description</span><span class="pi">:</span> <span class="s">A policy that allows users to manage their personal MFA configuration</span>
    <span class="na">Groups</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="kt">!Ref</span> <span class="s">AdminMFAGroup</span>
      <span class="c1"># add more groups that should have MFA requirement</span>
    <span class="na">ManagedPolicyName</span><span class="pi">:</span> <span class="s">ManageMFAPolicy</span>
    <span class="na">PolicyDocument</span><span class="pi">:</span>
      <span class="na">Version</span><span class="pi">:</span> <span class="s">2012-10-17</span>
      <span class="na">Statement</span><span class="pi">:</span>

      <span class="pi">-</span> <span class="na">Sid</span><span class="pi">:</span> <span class="s">AllowUsersToManageTheirOwnMFADevice</span>
        <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
        <span class="na">Action</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">iam:CreateVirtualMFADevice</span>
        <span class="pi">-</span> <span class="s">iam:EnableMFADevice</span>
        <span class="pi">-</span> <span class="s">iam:ResyncMFADevice</span>
        <span class="na">Resource</span><span class="pi">:</span>
          <span class="c1"># users should only manage their own resources</span>
          <span class="pi">-</span> <span class="kt">!Join</span> <span class="pi">[</span><span class="s1">'</span><span class="s">'</span><span class="pi">,</span> <span class="pi">[</span><span class="s1">'</span><span class="s">arn:aws:iam::'</span><span class="pi">,</span> <span class="kt">!Ref</span> <span class="s1">'</span><span class="s">AWS::AccountId'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">:mfa/${aws:username}'</span><span class="pi">]]</span>
          <span class="pi">-</span> <span class="kt">!Join</span> <span class="pi">[</span><span class="s1">'</span><span class="s">'</span><span class="pi">,</span> <span class="pi">[</span><span class="s1">'</span><span class="s">arn:aws:iam::'</span><span class="pi">,</span> <span class="kt">!Ref</span> <span class="s1">'</span><span class="s">AWS::AccountId'</span><span class="pi">,</span> <span class="s1">'</span><span class="s">:user/${aws:username}'</span><span class="pi">]]</span>

      <span class="pi">-</span> <span class="na">Sid</span><span class="pi">:</span> <span class="s">AllowUsersToListMFADevicesAndUsers</span>
        <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
        <span class="na">Action</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s">iam:ListMFADevices</span>
        <span class="pi">-</span> <span class="s">iam:ListVirtualMFADevices</span>
        <span class="pi">-</span> <span class="s">iam:ListUsers</span>
        <span class="na">Resource</span><span class="pi">:</span> <span class="s2">"</span><span class="s">*"</span>
</code></pre></div></div>

<h2 id="enforcing-mfa">Enforcing MFA</h2>

<p>Now the infrastructure is setup, and you can start enforcing MFA for your users.</p>

<blockquote>
  <p><strong>Warning</strong> This is the point where you should create a temporary admin user so that you can make corrections in case you lock yourself out from the account.</p>
</blockquote>

<ul>
  <li>Add all administrators to the <code class="language-plaintext highlighter-rouge">AdminMFAGroup</code>.</li>
  <li>Remove the <a href="https://console.aws.amazon.com/iam/home#policies/arn:aws:iam::aws:policy/AdministratorAccess">AdministratorAccess</a> policy from all users (if any).</li>
  <li>Remove the <a href="https://console.aws.amazon.com/iam/home#policies/arn:aws:iam::aws:policy/AdministratorAccess">AdministratorAccess</a> policy from all groups (if any).</li>
</ul>

<p>The result depend on whether or not the user already has an active MFA. Non-MFA users are now restricted to just enable MFA, both in the AWS Console and using the AWS CLI. As soon as they have an active MFA device (and have re-login), they will have admin privileges.</p>

<h2 id="aws-cli-profiles">AWS CLI Profiles</h2>

<p>All users of the account need to configure their AWS CLI environment with two profiles in order to facilitate the automatic role switch from the command line. Start by creating a <a href="http://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html">named profile</a> in your <code class="language-plaintext highlighter-rouge">~/.aws/credentials</code> file in which you provide the AWS Access Key ID and the AWS Secret Access Key:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>my-project]
<span class="nv">aws_access_key_id</span><span class="o">=</span>AKIAI44QH8DHBEXAMPLE
<span class="nv">aws_secret_access_key</span><span class="o">=</span>je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY
</code></pre></div></div>

<p>The second profile is created in the <code class="language-plaintext highlighter-rouge">~.aws/config</code> file in which you provide a reference to the profile to be use for authentication by using the <code class="language-plaintext highlighter-rouge">source_profile</code>, an ARN to the role which should be used for role switching and the ARN to your configured MFA device.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>profile my-project-mfa]
source_profile <span class="o">=</span> my-project
role_arn <span class="o">=</span> arn:aws:iam::123456789012:role/AdminMFARole
mfa_serial <span class="o">=</span> arn:aws:iam::123456789012:mfa/my-user-name
</code></pre></div></div>

<p>Of course, you need to change the values of the profile names, AWS Access Key ID, AWS Secret Access Key, role ARN, and the MFA serial to whatever values are applicable for you. One way of getting the MFA serial is to go to the IAM Users and check in the Security Credentials. It will be presented there if the user has configured a MFA device.</p>

<h2 id="usage">Usage</h2>

<p>Finally, all configuration is complete and you can start using your new, MFA enabled, CLI profile. To use a specific profile, you simply specify the profile name after using the <code class="language-plaintext highlighter-rouge">--profile</code> option. Continuing with the example profile names above, you will find that the <code class="language-plaintext highlighter-rouge">my-project</code> profile (i.e. the profile without MFA) is no longer allowed to for example list S3 buckets:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws s3 <span class="nb">ls</span> <span class="nt">--profile</span> my-project
An error occurred <span class="o">(</span>AccessDenied<span class="o">)</span> when calling the ListBuckets operation: Access Denied
</code></pre></div></div>

<p>Using the <code class="language-plaintext highlighter-rouge">my-project-mfa</code> profile on the other hand yields a different behavior:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>aws s3 <span class="nb">ls</span> <span class="nt">--profile</span> my-project-mfa
Enter MFA code:
<span class="o">[</span>user enters valid MFA token]
<span class="o">[</span>a list of S3 buckets is presented]
</code></pre></div></div>

<p>Achievement unlocked, requiring MFA for the AWS CLI! Many times you will execute multiple CLI commands to the same account. You can set the <code class="language-plaintext highlighter-rouge">AWS_PROFILE</code> environment variable to avoid to repeatedly key in the same profile over and over again, e.g.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">export </span><span class="nv">AWS_PROFILE</span><span class="o">=</span>my-project-mfa
</code></pre></div></div>

<h2 id="clean-up">Clean-up</h2>

<p>Do not forget to delete your temporary admin user.</p>

<h2 id="caveat">Caveat</h2>

<p>One caveat to look out for is that you may have noticed that specified the <code class="language-plaintext highlighter-rouge">AWS::AccountId</code> as <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal_specifying">Principal</a> in the <code class="language-plaintext highlighter-rouge">AdminMFARole</code>. As a consequence, all IAM users and roles in the acconut can assume this role (given that they satisfy the MFA condition). A much better approach would be to use the <code class="language-plaintext highlighter-rouge">AdminMFAGroup</code> as principal, regrettably AWS does not allow you to use <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal_specifying">IAM Group as a principal</a>. To mitigate this risk, you need to be very careful when you create policies for other groups, roles and users. For example you must make sure to restrict which roles they are allowed to switch to and they should be allowed to manage groups, roles or users. This should not be a surprise to you, if you have worked with AWS before you are probably already familiar with the <a href="http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege">principle of least privilege</a>.</p>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="security" /><category term="tools" /><category term="AWS" /><category term="CLI" /><category term="cloud" /><category term="CloudFormation" /><category term="MFA" /><category term="security" /><summary type="html"><![CDATA[Blog post that describes how you can use a CloudFormation Template and AWS CLI profiles to enforce MFA for AWS CLI users.]]></summary></entry><entry><title type="html">Introduction to Claudia.js</title><link href="https://matsev.github.io/blog/2016/11/06/introduction-to-claudia-js/" rel="alternate" type="text/html" title="Introduction to Claudia.js" /><published>2016-11-06T00:00:00+01:00</published><updated>2016-11-06T00:00:00+01:00</updated><id>https://matsev.github.io/blog/2016/11/06/introduction-to-claudia-js</id><content type="html" xml:base="https://matsev.github.io/blog/2016/11/06/introduction-to-claudia-js/"><![CDATA[<p>Serverless architecture and serverless applications are rapidly gaining momentum. In a couple of previous blog posts I have showed simple examples of how RESTful APIs can be deployed using AWS API Gateway and AWS Lambda. In <a href="/blog/2016/08/17/introduction-to-cloudformation-for-api-gateway/">one blog post</a> CloudFormation’s native resources were used to create the entire stack, including the RESTful API. In <a href="/blog/2016/09/18/introduction-to-swagger-for-cloudformation-and-api-gateway/">another blog post</a> a different approach was used where some resources remained in Lambda, but Swagger was used to define the RESTful API and the API Gateway mappings. Both alternatives provided full control of your stack, but the configurations were quite verbose even for simple sample projects. This time, we will re-implement the same application yet again, but instead of using CloudFormation we will take a look at what <a href="https://claudiajs.com/">Claudia.js</a> has to offer. The full source code is available in my GitHub <a href="https://github.com/matsev/claudiajs-demo">sample project</a>.</p>

<h2 id="tldr">tl;dr</h2>

<p>Claudia makes it easy to develop serverless applications that can be deployed to AWS. Execute the following steps in order to create a RESTful service that uses AWS API Gateway for HTTP request mapping and AWS Lambda for business logic:</p>

<ol>
  <li>Install Claudia as a global dependency by executing <code class="language-plaintext highlighter-rouge">$ npm install claudia --global</code>.</li>
  <li>Execute <code class="language-plaintext highlighter-rouge">$ npm init</code> in an empty directory to create a new npm project (enter appropriate answers for your project when prompted).</li>
  <li>Add Claudia API Builder as project dependency by executing <code class="language-plaintext highlighter-rouge">$ npm install claudia-api-builder --save</code>.</li>
  <li>
    <p>Create a new file called <code class="language-plaintext highlighter-rouge">index.js</code> in the project root folder with the following content:</p>

    <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
    
<span class="kd">const</span> <span class="nx">ApiBuilder</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">claudia-api-builder</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApiBuilder</span><span class="p">();</span>
    
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">api</span><span class="p">;</span>
    
<span class="nx">api</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/greeting</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Event:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">request</span><span class="p">));</span>
    <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">queryString</span><span class="p">.</span><span class="nx">name</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">World</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">return</span> <span class="p">{</span><span class="na">greeting</span><span class="p">:</span> <span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">};</span>
<span class="p">});</span>
</code></pre></div>    </div>
  </li>
  <li>Deploy your application to AWS <code class="language-plaintext highlighter-rouge">$ claudia create --region eu-west-1 --api-module index</code> (this step requires that you have an AWS account and that you have configured your access key ID and secret access key).</li>
  <li>
    <p>That’s it! That is all that is required to create an API Gateway backed by Lambda. To verify that it is working copy the url from the output of the previous command, append <code class="language-plaintext highlighter-rouge">/greeting</code> to it and make a request:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://[api-gateway-id].execute-api.eu-west-1.amazonaws.com/latest/greeting
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, World!"</span><span class="o">}</span>
</code></pre></div>    </div>
  </li>
  <li>Deployed and verified, wait for customers to start using your incredible greeting service. :-)</li>
</ol>

<h2 id="claudiajs">Claudia.js</h2>

<p>At its core, Claudia.js is a deployment tool. The following lines have been copied from the <a href="https://claudiajs.com/">project homepage</a>:</p>

<blockquote>
  <p>Claudia makes it easy to deploy Node.js projects to AWS Lambda and API Gateway. It automates all the error-prone deployment and configuration tasks, and sets everything up the way JavaScript developers expect out of the box.</p>
</blockquote>

<p>In addition, there is the <a href="https://claudiajs.com/claudia-api-builder.html">Claudia API Builder</a> that takes of the API Gateway generation, request and response mapping, etc:</p>

<blockquote>
  <p>Claudia API Builder is an extension library for Claudia.js that helps you get started with AWS API Gateway easily, and significantly reduces the learning curve required to launch web APIs in AWS. Instead of having to learn Swagger, manually managing request and response transformation templates, manually linking gateway methods to Lambda functions, you can just write the code for your API handlers, and Claudia API Builder takes care of the rest</p>
</blockquote>

<p>Behind the scenes, the <a href="https://aws.amazon.com/sdk-for-node-js/">AWS JavaScript SDK</a> is used to handle the deployment. By executing just a few commands, Claudia will create an application stack that comprises not only the API Gateway and the Lambda function in addition to the <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html">Lambda Proxy Integration</a> which glues the two together. Moreover, the required IAM role and the permission required for the API Gateway to invoke the Lambda are also created as part of the process.</p>

<h2 id="cli-tool">CLI Tool</h2>

<p>In order to work with Claudia, the CLI tool need to be installed. It can be installed as a global npm module, e.g. <code class="language-plaintext highlighter-rouge">$ npm install claudia --global</code> or you can install it as a test dependency <code class="language-plaintext highlighter-rouge">$ npm install claudia --save-dev</code> and then add the local <code class="language-plaintext highlighter-rouge">node_modules/.bin</code> directory to your <code class="language-plaintext highlighter-rouge">$PATH</code>. Verify by executing <code class="language-plaintext highlighter-rouge">claudia --version</code>. For a full list of the available command line args see the <a href="https://claudiajs.com/documentation.html">Command Args</a> documentation or execute <code class="language-plaintext highlighter-rouge">$ claudia --help</code>. As an alternative (or rather as a complement) to the CLI tool, one can also write some <a href="https://claudiajs.com/tutorials/package-json-scripts.html">deployment scripts</a> by adding a few lines to the <code class="language-plaintext highlighter-rouge">script</code> part of the <a href="https://github.com/matsev/claudiajs-demo/blob/master/package.json#L8-L9">package.json</a> file:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"create-app"</span><span class="p">:</span><span class="w"> </span><span class="s2">"claudia create --region eu-west-1 --api-module index"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"update"</span><span class="p">:</span><span class="w"> </span><span class="s2">"claudia update"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">create-app</code> script calls <a href="https://github.com/claudiajs/claudia/blob/master/docs/create.md">claudia create</a> command (see link for details) and similarly the <code class="language-plaintext highlighter-rouge">update</code> script calls the <a href="https://github.com/claudiajs/claudia/blob/master/docs/update.md">claudia update</a> command. To learn more about npm scripts, please read the blog <a href="https://blog.jayway.com/2014/03/28/running-scripts-with-npm/">Running scripts with npm</a> authored by my colleague Anders Janmyr.</p>

<h2 id="lambda-function">Lambda Function</h2>

<p>Before looking at the Lambda implementation, we create a new npm project by executing <code class="language-plaintext highlighter-rouge">$ npm init</code> and provide some good answers. Add the <code class="language-plaintext highlighter-rouge">claudia-api-builder</code> to the project, e.g. <code class="language-plaintext highlighter-rouge">$ npm install claudia-api-builder --save</code>. Now, we can copy / paste the following lines to an <a href="https://github.com/matsev/claudiajs-demo/blob/master/index.js">index.js</a> file in the project root:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
    
<span class="kd">const</span> <span class="nx">ApiBuilder</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">claudia-api-builder</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApiBuilder</span><span class="p">();</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">api</span><span class="p">;</span>

<span class="nx">api</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/greeting</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Event:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">request</span><span class="p">));</span>
    <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">queryString</span><span class="p">.</span><span class="nx">name</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">World</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">return</span> <span class="p">{</span><span class="na">greeting</span><span class="p">:</span> <span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">};</span>
<span class="p">});</span>
</code></pre></div></div>

<p>The API builder is used to define a <code class="language-plaintext highlighter-rouge">/greeting</code> resource that expects a <code class="language-plaintext highlighter-rouge">GET</code> request. If the string contains a query string parameter called <code class="language-plaintext highlighter-rouge">name</code> it is used as part of the response, otherwise <code class="language-plaintext highlighter-rouge">Hello, World!</code> is returned as the greeting response. The <a href="https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#the-request-object">request</a> object contains all information that was passed from the client, e.g. HTTP headers, request body, the <a href="http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html">Lambda context object</a>, the <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference">API Gateway context</a>, etc. The return value can either be a simple JavaScript object as exemplified above, but it can also be a <code class="language-plaintext highlighter-rouge">Promise</code> or more complex data structures, dynamic response, custom response headers and so on, see the <a href="https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#responding-to-requests">documentation</a> for details. In contrast to the native CloudFormation API Gateway implementation (and the CloudFormation Swagger configuration for that matter) the Claudia API Builder maps all API Gateway resources automatically to a single Lambda function. If you would like to have a richer REST api, you simply register more <a href="https://github.com/claudiajs/claudia-api-builder/blob/master/docs/api.md#api-definition-syntax">http handlers</a> with their paths by calling <code class="language-plaintext highlighter-rouge">get</code>, <code class="language-plaintext highlighter-rouge">put</code>, <code class="language-plaintext highlighter-rouge">post</code> or <code class="language-plaintext highlighter-rouge">delete</code>, very similar to how you would when you are developing a Node app that uses the Express web framework. In fact, with very little effort you can port an existing Express app to Claudia by just changing a couple of lines in the source code and importing the <code class="language-plaintext highlighter-rouge">aws-serverless-express</code> module, see <a href="https://claudiajs.com/tutorials/serverless-express.html">Running Express Apps in AWS Lambda</a> for details.</p>

<h2 id="deployment">Deployment</h2>

<p>Now we are ready to deploy. We do so by either calling the <a href="https://github.com/claudiajs/claudia/blob/master/docs/create.md">claudia create</a> command. As a result, we will see the details of the resource that were created:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>claudia create <span class="nt">--region</span> eu-west-1 <span class="nt">--api-module</span> index
<span class="o">[</span>...]
saving configuration
<span class="o">{</span>
    <span class="s2">"lambda"</span>: <span class="o">{</span>
    <span class="s2">"role"</span>: <span class="s2">"claudiajs-demo-executor"</span>,
    <span class="s2">"name"</span>: <span class="s2">"claudiajs-demo"</span>,
    <span class="s2">"region"</span>: <span class="s2">"eu-west-1"</span>
    <span class="o">}</span>,
    <span class="s2">"api"</span>: <span class="o">{</span>
    <span class="s2">"id"</span>: <span class="s2">"[api-gateway-id]"</span>,
    <span class="s2">"module"</span>: <span class="s2">"index"</span>,
    <span class="s2">"url"</span>: <span class="s2">"https://[api-gateway-id].execute-api.eu-west-1.amazonaws.com/latest"</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>where <code class="language-plaintext highlighter-rouge">[api-gateway-id]</code> is the id of your API Gateway. Alternatively we can use the npm script mentioned earlier with a similar result:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>npm run create-app
<span class="o">[</span>...]
saving configuration
<span class="o">[</span>...]
</code></pre></div></div>

<p>After the deployment, we can verify that the API Gateway and Lambda function has been deployed successfully:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://[api-gateway-id].execute-api.eu-west-1.amazonaws.com/latest/greeting
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, World!"</span><span class="o">}</span>
<span class="o">[</span>...]
<span class="nv">$ </span>curl https://[api-gateway-id].execute-api.eu-west-1.amazonaws.com/latest/greeting?name<span class="o">=</span>Superman
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, Superman!"</span><span class="o">}</span>
</code></pre></div></div>

<h2 id="considerations">Considerations</h2>

<ul>
  <li>There are other tools that can help with API Gateway and Lambda configuration and deployment such as <a href="https://github.com/serverless/serverless">Serverless</a> and <a href="http://senecajs.org/">Seneca</a> that you may find interesting. Start by reading the <a href="https://github.com/claudiajs/claudia/blob/master/FAQ.md#how-does-it-compare-to-">comparison</a> guide in the Claudia FAQ for a brief overview. There is also the <a href="https://github.com/awslabs/aws-serverless-express">aws-serverless-express</a> library from AWS Labs.</li>
  <li>A <code class="language-plaintext highlighter-rouge">claudia.json</code> file will be created after the initial call to <a href="https://github.com/claudiajs/claudia/blob/master/docs/create.md">claudia create</a>. This file contains your project specific Claudia configuration and should typically be added to version control so that your team mates and build server work against the same environment.</li>
  <li>If you need to start over (or shut down the application), there is the <a href="https://github.com/claudiajs/claudia/blob/master/docs/destroy.md">claudia destroy</a> command.</li>
  <li>As mentioned Claudia is a deployment tool that uses the AWS JavaScript SDK. It does not use CloudFormation and thus you cannot use CloudFormation to track your resources.</li>
  <li>Another consequence of not using CloudFormation is that you need to think twice about how you configure the Lambda functions when calling external services such as DynamoDB. The Claudia documentation provides a <a href="https://claudiajs.com/tutorials/external-services.html">checklist</a> for integration tips, including guidelines for <a href="https://claudiajs.com/tutorials/external-services.html#set-up-iam-access-privileges">setting up IAM privileges</a> and using <a href="https://claudiajs.com/tutorials/external-services.html#configure-external-access-keys-with-stage-variables">stage variables</a> for differentiation between environments. You can use a similar approach to detect the <a href="https://claudiajs.com/tutorials/versions.html#environment-configuration-without-api-gateway">environment configuration without API Gateway</a>.</li>
  <li>If you do not fancy the <code class="language-plaintext highlighter-rouge">claudia-api-builder</code>, you can still create a <a href="https://claudiajs.com/tutorials/deploying-proxy-api.html">proxy Lambda function</a> using Claudia. This can be useful if your clients use the Lambda API of an AWS SDK instead of a REST client when communicating with your backend.</li>
</ul>

<h2 id="more-resources">More Resources</h2>

<p>More information and links are available at the <a href="https://claudiajs.com/">Claudia.js</a> website, for example:</p>

<ul>
  <li><a href="https://claudiajs.com/documentation.html">Claudia Documentation</a>.</li>
  <li><a href="https://claudiajs.com/tutorials/hello-world-lambda.html">Hello World Lambda Tutorial</a>.</li>
  <li><a href="https://claudiajs.com/tutorials/hello-world-api-gateway.html">Hello World API Gateway Tutorial</a>.</li>
  <li><a href="https://github.com/claudiajs/example-projects">Claudia Example Projects at GitHub</a>.</li>
  <li><a href="https://gitter.im/claudiajs/claudia">The Claudia Gitter channel</a>.</li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="JavaScript" /><category term="API Gateway" /><category term="AWS" /><category term="cloud" /><category term="Cloudia.js" /><category term="JavaScript" /><category term="Lambda" /><category term="Node.js" /><category term="serverless" /><summary type="html"><![CDATA[Introduction to the Claudia.js deployment tool for AWS Lambda and AWS API Gateway. Some basic installation guides and a simple code example is provided.]]></summary></entry><entry><title type="html">Introduction to Swagger for CloudFormation and API Gateway</title><link href="https://matsev.github.io/blog/2016/09/18/introduction-to-swagger-for-cloudformation-and-api-gateway/" rel="alternate" type="text/html" title="Introduction to Swagger for CloudFormation and API Gateway" /><published>2016-09-18T00:00:00+02:00</published><updated>2016-09-18T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2016/09/18/introduction-to-swagger-for-cloudformation-and-api-gateway</id><content type="html" xml:base="https://matsev.github.io/blog/2016/09/18/introduction-to-swagger-for-cloudformation-and-api-gateway/"><![CDATA[<p>When I was writing my previous blog post about <a href="/blog/2016/08/17/introduction-to-cloudformation-for-api-gateway/">Introduction to CloudFormation for API Gateway</a>, I noticed that CloudFormation also supports <a href="http://swagger.io/">Swagger</a> for API Gateway configuration. Curious about what such an implementation would look like in comparison to the previous solution, I decided to give it a go. Like the previous example, consider this blog post and related source code as a proof of concept. If you choose to implement a similar solution, I advise you to study the reference docs using the provided links. The accompanying source code to this blog post can be found in my <a href="https://github.com/matsev/cloudformation-swagger-api-gateway">GitHub repo</a>.</p>

<h2 id="goal">Goal</h2>

<p>The aim of this blog post is to re-implement the very same example as in the <a href="/blog/2016/08/17/introduction-to-cloudformation-for-api-gateway/">previous post</a>, but this time use Swagger configuration instead of CloudFormation resources to configure the REST API. The business logic is an AWS Lambda function called <code class="language-plaintext highlighter-rouge">GreetingLambda</code> which has been configured with an appropriate execution role. If the event passed to the Lambda contains a <code class="language-plaintext highlighter-rouge">name</code> property, a JSON document with a greeting containing the name is returned. If not, the greeting <code class="language-plaintext highlighter-rouge">Hello, World!</code> is returned.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">context</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">name</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">World</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="p">{</span><span class="na">greeting</span><span class="p">:</span> <span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">};</span>
  <span class="nf">callback</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="nx">response</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>When proxied by an API Gateway, it should be possible to perform a HTTP request with the name as a request parameter.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting?name<span class="o">=</span>Superman
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, Superman!"</span><span class="o">}</span>
</code></pre></div></div>

<h2 id="cloudformation-resources">CloudFormation Resources</h2>

<p>Many of the CloudFormation resources will be left untouched from the previous implementation, so I will not describe them again (you will find them in the <a href="https://github.com/matsev/cloudformation-swagger-api-gateway/blob/master/cloudformation.template">cloudformation.template</a>). Let us focus on the differences instead.</p>

<h4 id="api-gateway-rest-api">API Gateway Rest API</h4>

<p>The biggest change is to the <code class="language-plaintext highlighter-rouge">AWS::ApiGateway::RestApi</code> resource. By configuring the <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-body">Body</a> property it is possible to inline the entire REST API using <a href="http://swagger.io/">Swagger</a>’s JSON format. As a result, the CloudFormation template as a whole will be less verbose since some other CloudFormation resources can be deleted. That said, it is still a mouthful when you include the <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html">API Gateway Extensions to Swagger</a> to handle the integration between the API Gateway and other AWS services.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"GreetingApi"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::ApiGateway::RestApi"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Greeting API"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"API used for Greeting requests"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FailOnWarnings"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Body"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"swagger"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2016-08-18T18:08:34Z"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Greeting API"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"basePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/LATEST"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"schemes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"https"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"/greeting"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"get"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
              </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"name"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"in"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"required"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
              </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="w">
            </span><span class="p">}],</span><span class="w">
            </span><span class="nl">"produces"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"application/json"</span><span class="p">],</span><span class="w">
            </span><span class="nl">"responses"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"200"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"200 response"</span><span class="w">
              </span><span class="p">}</span><span class="w">
            </span><span class="p">},</span><span class="w">
            </span><span class="nl">"x-amazon-apigateway-integration"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
              </span><span class="nl">"requestTemplates"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"application/json"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{</span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">$input.params('name')</span><span class="se">\"</span><span class="s2">}"</span><span class="w">
              </span><span class="p">},</span><span class="w">
              </span><span class="nl">"uri"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Fn::Join"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="w">
                </span><span class="s2">"arn:aws:apigateway:"</span><span class="p">,</span><span class="w">
                </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Region"</span><span class="p">},</span><span class="w">
                </span><span class="s2">":lambda:path/2015-03-31/functions/"</span><span class="p">,</span><span class="w">
                </span><span class="p">{</span><span class="nl">"Fn::GetAtt"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"GreetingLambda"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Arn"</span><span class="p">]},</span><span class="w">
                </span><span class="s2">"/invocations"</span><span class="p">]</span><span class="w">
              </span><span class="p">]},</span><span class="w">
              </span><span class="nl">"responses"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"default"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                  </span><span class="nl">"statusCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"200"</span><span class="w">
                </span><span class="p">}</span><span class="w">
              </span><span class="p">},</span><span class="w">
              </span><span class="nl">"httpMethod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"POST"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"aws"</span><span class="w">
            </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Configuration comments:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Body</code> Root node of the Swagger configuration. Basically a JSON document that conforms to the <a href="http://swagger.io/specification/">Swagger 2.0 Specification</a>.</li>
  <li><code class="language-plaintext highlighter-rouge">/greeting</code> Defines the greeting endpoint and its behavior, e.g. it accepts HTTP <code class="language-plaintext highlighter-rouge">GET</code> requests, it has an optional query parameter called <code class="language-plaintext highlighter-rouge">name</code>, it responds with <code class="language-plaintext highlighter-rouge">200 - OK</code> and the response is in JSON format.</li>
  <li><a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html#api-gateway-swagger-extensions-integration">x-amazon-apigateway-integration</a> A big part of the code consists of the configuration that is responsible for mapping and transforming the request and response from the RESTful endpoint to the specified AWS service. It contains several sub-properties, more than the ones listed below.
    <ul>
      <li><a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html#api-gateway-swagger-extensions-integration-requestTemplates">requestTemplates</a> This part of the configuration transforms the incoming request before passing the result downstream då the Lambda function. In this example, the value of the <code class="language-plaintext highlighter-rouge">name</code> query parameter is transformed into a JSON object that has the format <code class="language-plaintext highlighter-rouge">{"name": "Superman"}</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">uri</code> The backend endpoint, here exemplified by the <code class="language-plaintext highlighter-rouge">GreetingLambda</code> ARN.</li>
      <li><a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html#api-gateway-swagger-extensions-integration-responses">responses</a> Response status patterns from the backend service. Each key has then a <code class="language-plaintext highlighter-rouge">statusCode</code> property, which in turn must have a matching status code in the Swagger operation <code class="language-plaintext highlighter-rouge">responses</code>. Put differently, this is the HTTP status code that will be returned to the client. In the sample above, you will see that <code class="language-plaintext highlighter-rouge">default</code> is used as status pattern and <code class="language-plaintext highlighter-rouge">200</code> as status code, meaning that the client will always get a <code class="language-plaintext highlighter-rouge">200 - OK</code> as a result, regardless if the Lambda call was successful or not. This is something you likely want to change in a production project.</li>
      <li><code class="language-plaintext highlighter-rouge">httpMethod</code> and <code class="language-plaintext highlighter-rouge">type</code> defaults to HTTP <code class="language-plaintext highlighter-rouge">POST</code> and <code class="language-plaintext highlighter-rouge">aws</code> when the API Gateway proxies AWS Lambda.</li>
    </ul>
  </li>
  <li>In addition to the <code class="language-plaintext highlighter-rouge">x-amazon-apigateway-integration</code> object the <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html">API Gateway Extensions to Swagger</a> also have support for other properties such as custom authorization configuration.</li>
</ul>

<p>Ref: <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html">AWS::ApiGateway::RestApi</a></p>

<h4 id="redundant-cloudformation-resources">Redundant CloudFormation Resources</h4>

<p>By having all the RESTful endpoints declared in Swagger format, one can omit the <code class="language-plaintext highlighter-rouge">AWS::ApiGateway::Resource</code> and the <code class="language-plaintext highlighter-rouge">AWS::ApiGateway::Method</code> resources.</p>

<h2 id="test">Test</h2>

<p>Similarly as in the previous blog post, the API Gateway gets a root url of the <code class="language-plaintext highlighter-rouge">https://[api-id].execute-api.[region].amazonaws.com</code> format. If you create a stack based on the complete <a href="https://github.com/matsev/cloudformation-swagger-api-gateway/blob/master/cloudformation.template">cloudformation.template</a> you will see the exact url in the stack output. To verify that the stack works, you have to append the <code class="language-plaintext highlighter-rouge">basePath</code> and the <code class="language-plaintext highlighter-rouge">/greeting</code> path to it before issuing a HTTP GET request.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, World!"</span><span class="o">}</span>
</code></pre></div></div>

<p>You can also provide a request parameter:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl https://abc123.execute-api.eu-west-1.amazonaws.com/LATEST/greeting?name<span class="o">=</span>Superman
<span class="o">{</span><span class="s2">"greeting"</span>:<span class="s2">"Hello, Superman!"</span><span class="o">}</span>
</code></pre></div></div>

<h2 id="considerations">Considerations</h2>

<ul>
  <li>In this example, the Swagger configuration was inlined in the CloudFormation Template. If you prefer to keep things separate one can upload a Swagger file to S3 and point the <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-bodys3location">BodyS3Location</a> property of the <code class="language-plaintext highlighter-rouge">AWS::ApiGateway::RestApi</code> to it. One advantage of using this property is that the file can be in either YAML or JSON format. Another benefit of keeping the Swagger configuration separate from the CloudFormation template is that you can pass the very same Swagger file to the client developers and they can use tools such as the <a href="https://github.com/swagger-api/swagger-codegen#swagger-code-generator">Swagger Code Generator</a> to generate client APIs and documentation. A minor disadvantage is of course that you need keep two files in sync when you deploy a new API Gateway Stage.</li>
  <li>Swagger enables “contract first design”, i.e. you can define a RESTful API before any client or server business logic exists. It is an opinionated way of working and I usually prefer to develop the API and app incrementally. The good news is that Swagger can be used by both parties.</li>
  <li>It is easy to prototype a new API Gateway using “point and click” in the AWS Console. When you are finished you can choose to <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-export-api.html">Export an API from API Gateway</a>, including the API Gateway Integration, using either a HTTP Get request or the AWS Console. Once you get hold of the Swagger file, you can paste it into your CloudFormation template or save it in an S3 bucket. Preferably, you take the opportunity to do some pruning in the process, for example you can replace any string that contains an AWS region with <code class="language-plaintext highlighter-rouge">{"Ref": "AWS::Region"}</code>, any string that contains a reference to another CloudFormation resource with <code class="language-plaintext highlighter-rouge">{"Ref": "&lt;logical resource id&gt;"}</code>, <code class="language-plaintext highlighter-rouge">{"Fn::GetAtt": ["&lt;Lambda logical id&gt;", "Arn"]}</code> for retrieving a Lambda ARN, etc.</li>
</ul>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="DevOps" /><category term="tools" /><category term="API Gateway" /><category term="AWS" /><category term="cloud" /><category term="CloudFormation" /><category term="DevOps" /><category term="Lambda" /><category term="serverless" /><category term="Swagger" /><summary type="html"><![CDATA[Blog post that outlines how Swagger can be used to configure an AWS API Gateway backed by an AWS Lambda function.]]></summary></entry><entry><title type="html">Continuous Deployment of AWS Lambda behind API Gateway</title><link href="https://matsev.github.io/blog/2016/09/07/continuous-deployment-of-aws-lambda-behind-api-gateway/" rel="alternate" type="text/html" title="Continuous Deployment of AWS Lambda behind API Gateway" /><published>2016-09-07T00:00:00+02:00</published><updated>2016-09-07T00:00:00+02:00</updated><id>https://matsev.github.io/blog/2016/09/07/continuous-deployment-of-aws-lambda-behind-api-gateway</id><content type="html" xml:base="https://matsev.github.io/blog/2016/09/07/continuous-deployment-of-aws-lambda-behind-api-gateway/"><![CDATA[<p>In two previous blog posts I started by introducing scripts for <a href="/blog/2016/07/07/continuous-deployment-on-aws-lambda/">Continuous Deployment on AWS Lambda</a> and then I continued to experiment with <a href="/blog/2016/08/17/introduction-to-cloudformation-for-api-gateway/">Introduction to CloudFormation for API Gateway</a>. This blog post will be a continuation of both of them. You will see an example of how continuous deployment of an AWS Lambda function behind an AWS API Gateway may look like in a CloudFormation setup. All implementation details can found in the <a href="https://github.com/matsev/api-gateway-continuous-deployment">sample repository</a> at GitHub if you are interested.</p>

<h2 id="goal">Goal</h2>

<p>By revisiting the <a href="/blog/2016/07/07/continuous-deployment-on-aws-lambda/">first blog post</a>, you can see an example of how <a href="http://docs.aws.amazon.com/lambda/latest/dg/versioning-aliases.html">AWS Lambda Function Versioning and Aliases</a> can be used to implement continuous delivery on Lambda. In short, two different Lambda alias, <code class="language-plaintext highlighter-rouge">STAGE</code> and <code class="language-plaintext highlighter-rouge">PROD</code>, were used as two different deployment targets for the Lambda function. Some scripts were developed that first executed unit tests of the Lambda source code, zipped the Lambda source code to a new package that was deployed a new Lambda version and updated the Lambda <code class="language-plaintext highlighter-rouge">STAGE</code> alias if the Lambda integration tests passed. In the <a href="/blog/2016/08/17/introduction-to-cloudformation-for-api-gateway/">second blog post</a>, I showed how CloudFormation can be used to configure an API Gateway backed by a Lambda function. This blog post aims to combine the two previous blog posts to create two API Gateway stages, the first named <code class="language-plaintext highlighter-rouge">stage</code>, the second named <code class="language-plaintext highlighter-rouge">prod</code>, that will be mapped to each Lambda alias respectively. In addition, new integration tests should be developed that verify that the RESTful API works as expected.</p>

<h2 id="cloudformation-resources">CloudFormation Resources</h2>

<p>Some changes to the CloudFormation template are required in order to make the continuous deployment work. The final result can be found in the <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/cloudformation.template">cloudformation.template</a> at GitHub.</p>

<h4 id="lambda-aliases">Lambda Aliases</h4>

<p>As with any other AWS resource, one must add permission before a Lambda function can be called. More to the point, in the <a href="http://docs.aws.amazon.com/lambda/latest/dg/versioning-aliases-permissions.html">Versioning, Aliases, and Resource Policies</a> section of the AWS Lambda Developer Guide, it is stated that one must add a permission based on the Lambda alias ARN in order to invoke the Lambda using an alias name. Any attempt to use a permission based on the unqualified Lambda ARN will result in a permission error. For this reason two <code class="language-plaintext highlighter-rouge">AWS::Lambda::Alias</code> resources are created up front that will be used later when creating the Lambda permissions.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"GreetingStageLambdaAlias"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"Type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Lambda::Alias"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"FunctionName"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingLambda"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"FunctionVersion"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"$LATEST"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"STAGE"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">

</span><span class="nl">"GreetingProdLambdaAlias"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"Type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Lambda::Alias"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"FunctionName"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingLambda"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"FunctionVersion"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"$LATEST"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PROD"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Both aliases reference the same Lambda <code class="language-plaintext highlighter-rouge">FunctionName</code>, but they have different alias <code class="language-plaintext highlighter-rouge">Name</code>s. The <code class="language-plaintext highlighter-rouge">FunctionVersion</code> has been set to <code class="language-plaintext highlighter-rouge">$LATEST</code> version which means that both alias will point to the <a href="http://docs.aws.amazon.com/lambda/latest/dg/aliases-intro.html">latest Lambda version</a> after the initial stack deployment. Later, the aliases will be updated by the continuous deployment scripts to point to new versions. Ref: <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html">AWS::Lambda::Alias</a></p>

<h4 id="lambda-permissions">Lambda Permissions</h4>

<p>After the aliases have been created, two <code class="language-plaintext highlighter-rouge">AWS::Lambda::Permission</code>s are created to allow the API Gateway to call both <code class="language-plaintext highlighter-rouge">GreetingLambda:STAGE</code> and <code class="language-plaintext highlighter-rouge">GreetingLambda:PROD</code> alias.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"GreetingLambdaStagePermission"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Lambda::Permission"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lambda:invokeFunction"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FunctionName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingLambdaStageAlias"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"Principal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"apigateway.amazonaws.com"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SourceArn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Fn::Join"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">""</span><span class="p">,</span><span class="w">
      </span><span class="p">[</span><span class="s2">"arn:aws:execute-api:"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Region"</span><span class="p">},</span><span class="w"> </span><span class="s2">":"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::AccountId"</span><span class="p">},</span><span class="w"> </span><span class="s2">":"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingApi"</span><span class="p">},</span><span class="w"> </span><span class="s2">"/*"</span><span class="p">]</span><span class="w">
    </span><span class="p">]}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">

</span><span class="nl">"GreetingLambdaProdPermission"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Lambda::Permission"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"Action"</span><span class="p">:</span><span class="w"> </span><span class="s2">"lambda:invokeFunction"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"FunctionName"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingLambdaProdAlias"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"Principal"</span><span class="p">:</span><span class="w"> </span><span class="s2">"apigateway.amazonaws.com"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"SourceArn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Fn::Join"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">""</span><span class="p">,</span><span class="w">
      </span><span class="p">[</span><span class="s2">"arn:aws:execute-api:"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Region"</span><span class="p">},</span><span class="w"> </span><span class="s2">":"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::AccountId"</span><span class="p">},</span><span class="w"> </span><span class="s2">":"</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingApi"</span><span class="p">},</span><span class="w"> </span><span class="s2">"/*"</span><span class="p">]</span><span class="w">
    </span><span class="p">]}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Note that we pass references to the two Lambda aliases instead of the Lambda resource as <code class="language-plaintext highlighter-rouge">FunctionName</code> values. Ref: <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html">AWS::Lambda::Permission</a></p>

<h4 id="api-gateway-stages">API Gateway Stages</h4>

<p>Now we have all required components to create the API Gateway stages. They are very similar to the example developed in the previous blog post, but with one important distinction. By defining a <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html">Stage Variables</a> called <code class="language-plaintext highlighter-rouge">LambdaAlias</code> we can add a value for mapping the Lambda alias later on. Stage variables act as environment variables in your API Gateway configuration. You can use whatever variable name and value that you like, as long as you use the <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-variables">allowed characters</a>. Please see <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/aws-api-gateway-stage-variables-reference.html">API Gateway Stage Variables Reference</a> for details and use cases of how stage variables can be used.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"GreetingApiStageStage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"DependsOn"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ApiGatewayAccount"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::ApiGateway::Stage"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"DeploymentId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ApiDeployment"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"MethodSettings"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
      </span><span class="nl">"DataTraceEnabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"HttpMethod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"LoggingLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"INFO"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ResourcePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/*"</span><span class="w">
    </span><span class="p">}],</span><span class="w">
    </span><span class="nl">"RestApiId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingApi"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"StageName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stage"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"LambdaAlias"</span><span class="p">:</span><span class="w"> </span><span class="s2">"STAGE"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">

</span><span class="nl">"GreetingApiProdStage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"DependsOn"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"ApiGatewayAccount"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::ApiGateway::Stage"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"DeploymentId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ApiDeployment"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"MethodSettings"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
      </span><span class="nl">"DataTraceEnabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"HttpMethod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"*"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"LoggingLevel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"INFO"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ResourcePath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/*"</span><span class="w">
    </span><span class="p">}],</span><span class="w">
    </span><span class="nl">"RestApiId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingApi"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"StageName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prod"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"LambdaAlias"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PROD"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<h4 id="api-gateway-method">API Gateway Method</h4>

<p>The last change of the CloudFormation template compared to the previous blog post is in the API Gateway method resource where the HTTP method is mapped to the Lambda function. Previously, the <a href="http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-uri">API Gateway Uri</a> was “just” a magic string including the Lambda ARN. With the addition of the <code class="language-plaintext highlighter-rouge">${stageVariables.LambdaAlias}</code> stage variable to provide the Lambda alias the string becomes even more magic. Depending on which API Gateway stage is being called, the stage variable will be evaluated to <code class="language-plaintext highlighter-rouge">STAGE</code> and <code class="language-plaintext highlighter-rouge">PROD</code> respectively, thus directing the request to the matching Lambda alias. Please see the <a href="http://docs.aws.amazon.com/apigateway/latest/developerguide/aws-api-gateway-stage-variables-reference.html#stage-variables-in-integration-lambda-functions">AWS Integration URIs (Lambda Functions)</a> for more details on how this works.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"GreetingRequest"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"DependsOn"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LambdaPermission"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::ApiGateway::Method"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"Properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"AuthorizationType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"NONE"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"HttpMethod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GET"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"Integration"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"IntegrationHttpMethod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"POST"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"Uri"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Fn::Join"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">""</span><span class="p">,</span><span class="w">
        </span><span class="p">[</span><span class="s2">"arn:aws:apigateway:"</span><span class="p">,</span><span class="w">
        </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AWS::Region"</span><span class="p">},</span><span class="w">
        </span><span class="s2">":lambda:path/2015-03-31/functions/"</span><span class="p">,</span><span class="w">
        </span><span class="p">{</span><span class="nl">"Fn::GetAtt"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"GreetingLambda"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Arn"</span><span class="p">]},</span><span class="w">
        </span><span class="s2">":${stageVariables.LambdaAlias}"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"/invocations"</span><span class="p">]</span><span class="w">
      </span><span class="p">]},</span><span class="w">
      </span><span class="nl">"IntegrationResponses"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
        </span><span class="nl">"StatusCode"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="w">
      </span><span class="p">}],</span><span class="w">
      </span><span class="nl">"RequestTemplates"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"application/json"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Fn::Join"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">""</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="s2">"{"</span><span class="p">,</span><span class="w">
          </span><span class="s2">"  </span><span class="se">\"</span><span class="s2">name</span><span class="se">\"</span><span class="s2">: </span><span class="se">\"</span><span class="s2">$input.params('name')</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span><span class="w">
          </span><span class="s2">"}"</span><span class="w">
        </span><span class="p">]]}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"RequestParameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"method.request.querystring.name"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"ResourceId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingResource"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"RestApiId"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"Ref"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GreetingApi"</span><span class="p">},</span><span class="w">
    </span><span class="nl">"MethodResponses"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="w">
      </span><span class="nl">"StatusCode"</span><span class="p">:</span><span class="w"> </span><span class="mi">200</span><span class="w">
    </span><span class="p">}]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="tests">Tests</h2>

<p>After the CloudFormation template, the application can be tested in three different levels. First of all, there is the <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/test/greeter-test.js">unit test</a> at the JavaScript level (executor <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/scripts/1-test.sh">1-test.sh</a>). Next level <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/itest-lambda/greeting-lambda-test.js">tests the Lambda function</a> using the AWS JavaScript SDK after a new Lambda function has been updated (executor <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/scripts/4-lambda-itest.sh">4-lambda-itest.sh</a>) and acts as a gatekeeper whether or not the Lambda alias should be updated. The last level of <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/itest-api-gateway/greeting-rest-test.js">integration tests</a> uses a REST client to send requests to the API Gateway and validates the responses (executor <a href="https://github.com/matsev/api-gateway-continuous-deployment/blob/master/scripts/7-api-gateway-itest.sh">7-api-gateway-itest.sh</a>).</p>

<h2 id="consideration">Consideration</h2>

<p>The continuous integration flow has only been configured to update the Lambda implementation and not the API Gateway stage. Consequently, if you update your Lambda function in a way that requires changes to the API Gateway integration request or response mapping, the REST client integration tests will not work, despite appropriate updates. The cause of this error is that once the API Gateway stage has been been deployed, it cannot be updated. If you find the need for such update, you must either deploy a new stage, or delete the existing stage, make the necessary changes and re-deploy it.</p>

<h2 id="update">Update</h2>

<p>On the November 18th, 2016 AWS introduced the Serverless Application Model (or SAM for short) that provides an alternative solution to the one described in this blog post. Please read the <a href="https://aws.amazon.com/blogs/compute/introducing-simplified-serverless-application-deplyoment-and-management/">AWS blog post</a> and study the related <a href="https://github.com/awslabs/serverless-application-model/blob/master/README.md">project</a> at the AWS Labs GitHub account for more information.</p>]]></content><author><name>Mattias Severson</name></author><category term="cloud" /><category term="DevOps" /><category term="testing" /><category term="API Gateway" /><category term="AWS" /><category term="bash" /><category term="CI/CD" /><category term="cloud" /><category term="CloudFormation" /><category term="DevOps" /><category term="Lambda" /><category term="serverless" /><summary type="html"><![CDATA[Blog about how bash scripts can be used for continuous deployment of an AWS Lambda that is behind an API Gateway. Contains links to GitHub sample project.]]></summary></entry></feed>