<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Taylor Reece</title>
    <description>The latest articles on DEV Community by Taylor Reece (@taylorreece).</description>
    <link>https://dev.to/taylorreece</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501945%2Fd325f92d-44ed-447b-96fe-e9de3588969c.jpeg</url>
      <title>DEV Community: Taylor Reece</title>
      <link>https://dev.to/taylorreece</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/taylorreece"/>
    <language>en</language>
    <item>
      <title>Six OAuth 2.0 Anti-Patterns to Avoid</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Thu, 19 Mar 2026 19:48:42 +0000</pubDate>
      <link>https://dev.to/taylorreece/six-oauth-20-anti-patterns-to-avoid-1g9e</link>
      <guid>https://dev.to/taylorreece/six-oauth-20-anti-patterns-to-avoid-1g9e</guid>
      <description>&lt;p&gt;I like to joke that developers who add the OAuth 2.0 authentication code flow to their apps read &lt;a href="https://oauth.net/2/grant-types/authorization-code/" rel="noopener noreferrer"&gt;the spec&lt;/a&gt;, implement 90% of it, and then simply wing the last 10%. I say it in jest, but it's not far from the truth.&lt;/p&gt;

&lt;p&gt;After building over 200 connectors (84 of which use OAuth 2.0 authentication), we've seen dozens of deviations from the spec that you &lt;em&gt;absolutely should not&lt;/em&gt; mimic when adding OAuth 2.0 to your app.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OAuth 2.0?
&lt;/h2&gt;

&lt;p&gt;But, before we cover what OAuth 2.0 shouldn't be, let's see what it is. OAuth 2.0 is a standard that allows one app to access data in another app on your behalf.&lt;/p&gt;

&lt;p&gt;If you've ever clicked a "Log in with Google" button or selected "Click here to connect your Outlook calendar", you've likely gone through an OAuth flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should OAuth 2.0 auth code work?
&lt;/h3&gt;

&lt;p&gt;Suppose you're logged in to Acme. Acme has an integration with Dropbox, and to enable it, you click a handy button labeled "Click here to link your Dropbox account." Clicking that button will bring you to Dropbox's &lt;strong&gt;Authorize URL&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.dropbox.com/oauth2/authorize?client_id=abc-123&amp;amp;state=foo&amp;amp;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you arrive at Dropbox, you'll be presented with a &lt;strong&gt;consent screen&lt;/strong&gt; – a screen that says something like "Acme would like access to files in thus-and-such Dropbox folder. Cool?" You're familiar with these screens, I'm sure.&lt;/p&gt;

&lt;p&gt;Once you select "Yeah, grant access to Acme," Dropbox sends you back to Acme's &lt;strong&gt;Callback URL&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://oauth2.acme.com/callback?state=foo&amp;amp;code=bar1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, Acme has your "auth code" (the &lt;code&gt;code=bar1234&lt;/code&gt; bit). Acme can now use Dropbox's &lt;strong&gt;Token URL&lt;/strong&gt; to exchange that code (along with a &lt;code&gt;client_secret&lt;/code&gt;) for an &lt;code&gt;access_token&lt;/code&gt; and sometimes a &lt;code&gt;refresh_token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From there, Acme can use your access token to do whatever you permitted it to do, all without ever handing Acme your Dropbox username and password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;p&gt;The OAuth 2.0 authorization code spec is pretty straightforward: users are redirected through an &lt;strong&gt;Authorize URL&lt;/strong&gt;, consent to some things, return to the calling app with an auth code, and the auth code is exchanged for an access token.&lt;/p&gt;

&lt;p&gt;Straightforward, yes? Let's look at some common ways you can deviate from that simple pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token expiration in nanoseconds?
&lt;/h3&gt;

&lt;p&gt;When an app completes the auth code exchange, it receives a payload that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2YotnFZFEjr1zCsicMWpAA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tGzv3JOkF0XG5Qx2TlKWIA"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;expires_in&lt;/code&gt; property is defined in &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2" rel="noopener noreferrer"&gt;section 4.2.2 of the OAuth 2.0 spec&lt;/a&gt; as "The lifetime in &lt;strong&gt;seconds&lt;/strong&gt; of the access token."&lt;/p&gt;

&lt;p&gt;Guess what happened when one of our customers got back an &lt;code&gt;expires_in&lt;/code&gt; of 3,600,000,000,000? You guessed it. Our OAuth service set itself a reminder to refresh the token in 3.6 trillion seconds (or 114,077 &lt;em&gt;years&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Obviously, that was not the app developer's intention. They were just pedantic and loved the precision of nanoseconds over seconds. However, doing it this way is an anti-pattern, and resulted in this lovely bit of code in our OAuth 2.0 service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Apparently some people feel the inexplicable need to use nanoseconds as&lt;/span&gt;
&lt;span class="c1"&gt;// the unit of measure for the expiration time. For now I think the best&lt;/span&gt;
&lt;span class="c1"&gt;// we can do is recognize a "stupidly large" value and assume that it's&lt;/span&gt;
&lt;span class="c1"&gt;// probably using a silly unit of measure, and "fix" it. We'll do this&lt;/span&gt;
&lt;span class="c1"&gt;// for nanoseconds for now, as guessing should be pretty safe.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intExpiresIn&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="nx"&gt;_540_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// If it looks like the number of seconds is more than 1000 years,&lt;/span&gt;
  &lt;span class="c1"&gt;// it's quite likely that the unit of measure is something dumb&lt;/span&gt;
  &lt;span class="c1"&gt;// like nanoseconds. So divide it by a billion...&lt;/span&gt;
  &lt;span class="nx"&gt;intExpiresIn&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token expiration in seconds vs at time
&lt;/h3&gt;

&lt;p&gt;The spec clearly calls for &lt;code&gt;expires_in&lt;/code&gt; – the number of seconds from now until the token expires. Despite that, some apps will try to save you some compute cycles by returning an &lt;code&gt;expires_at&lt;/code&gt; timestamp instead. They give you something like &lt;code&gt;"expires_at": "2026-03-05T15:36:41.304Z"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The spec doesn't mention an &lt;code&gt;expires_at&lt;/code&gt; value, so it's something extra to account for. To compound the problem, there's no standardization of the date/time format for &lt;code&gt;expires_at&lt;/code&gt;. While some apps give you a UTC timestamp in &lt;a href="https://en.wikipedia.org/wiki/ISO_8601" rel="noopener noreferrer"&gt;ISO 8601 format&lt;/a&gt;, others yield the number of seconds (nanoseconds?) since &lt;a href="https://en.wikipedia.org/wiki/Unix_time" rel="noopener noreferrer"&gt;Unix Epoch&lt;/a&gt;, which means you need to do some amount of computation anyway, and leads to this part of our OAuth 2.0 service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Handle the situation where we got an expires_at but no expires_in&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;intExpiresIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auth code search parameters vs fragments
&lt;/h3&gt;

&lt;p&gt;When you return to your original app's callback URL with a &lt;code&gt;?code=some-auth-code&lt;/code&gt; search parameter, the server that responds to the callback request exchanges the auth code for an access token. &lt;a href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2" rel="noopener noreferrer"&gt;Section 4.1.2 of the spec&lt;/a&gt; is clear: this needs to be a URL query parameter.&lt;/p&gt;

&lt;p&gt;Despite that, some apps opt to return a URI fragment (so, &lt;code&gt;/callback#code=some-auth-code&lt;/code&gt; instead). Their justification is that fragments aren't stored in browser history (unlike search parameters), so your auth code is more secure. But auth codes are supposed to be invalidated shortly after issuance anyway – so not really a problem.&lt;/p&gt;

&lt;p&gt;This creates a headache because fragments aren't readable by the callback server. Instead, the frontend needs to read the fragment and act on it.&lt;/p&gt;

&lt;p&gt;To work around this, our customers have created an intermediary endpoint that includes some HTML/JS that converts the fragment into a search parameter, then redirects to our callback URL with the auth code as a search parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming conventions
&lt;/h3&gt;

&lt;p&gt;Have you ever gotten into an argument about &lt;code&gt;snake_case&lt;/code&gt; vs &lt;code&gt;camelCase&lt;/code&gt; vs &lt;code&gt;kebab-case&lt;/code&gt;? Developers have &lt;em&gt;strong&lt;/em&gt; opinions on this – especially if they have backgrounds in different tech stacks.&lt;/p&gt;

&lt;p&gt;When you're creating an OAuth 2.0 service, though, your opinion on the topic doesn't matter – the spec requires &lt;code&gt;snake_case&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Despite this, some apps use &lt;code&gt;clientId&lt;/code&gt; or &lt;code&gt;client-id&lt;/code&gt; instead of the correct &lt;code&gt;client_id&lt;/code&gt;; ditto for &lt;code&gt;grantType&lt;/code&gt; or &lt;code&gt;client-secret&lt;/code&gt;. Others make up an entirely new term, like &lt;code&gt;appSecretKey&lt;/code&gt; for &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Please use &lt;code&gt;snake_case&lt;/code&gt;. OAuth libraries don't map fields gracefully, and using anything other than what's specified requires special treatment for anyone wanting to integrate with your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error responses
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6749#section-5.2" rel="noopener noreferrer"&gt;Section 5.2 of the OAuth spec&lt;/a&gt; outlines the expected error structure when an error occurs during token exchange. You need to provide an error that is an enum with a value like &lt;code&gt;invalid_scope&lt;/code&gt; or &lt;code&gt;unauthorized_client&lt;/code&gt;. If you need to provide more details, an optional &lt;code&gt;error_description&lt;/code&gt; string can be included. An error may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_scope"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You asked for a scope of 'widgets.red' - do you mean 'widgets.read'?"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OAuth 2.0 libraries expect this format. Despite the clear spec, I've seen apps throw standards to the wind and return nested objects like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid token"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes debugging tricky. A &lt;code&gt;console.debug(error)&lt;/code&gt; yields an oh-so-helpful &lt;code&gt;[object Object]&lt;/code&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Made-up OAuth 2.0 flows
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;grant_type&lt;/code&gt; defines the OAuth 2.0 flavor you're using, and &lt;code&gt;authorization_code&lt;/code&gt; is pretty standard for auth that requires user interaction. Other apps use &lt;code&gt;client_credentials&lt;/code&gt; (machine-to-machine OAuth). Some older apps use the deprecated &lt;code&gt;password&lt;/code&gt; OAuth flow.&lt;/p&gt;

&lt;p&gt;Only one app on the internet uses the &lt;code&gt;account_credentials&lt;/code&gt; flow – a made-up OAuth flow that's really &lt;code&gt;client_credentials&lt;/code&gt; under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  On to butterflies, now that we've handled snakes
&lt;/h2&gt;

&lt;p&gt;If you're going to add OAuth 2.0 to your app, please read the spec and then follow it. Deviation from that doesn't make you a special butterfly, but it does make your app an edge case that anyone who integrates with it must explicitly handle.&lt;/p&gt;

&lt;p&gt;A good practice to follow, once you have an OAuth 2.0 service in place, is to grab a few off-the-shelf OAuth clients, like &lt;a href="https://www.npmjs.com/package/simple-oauth2" rel="noopener noreferrer"&gt;simple-oauth2&lt;/a&gt; for Node.js or &lt;a href="https://oauth2-client.thephpleague.com/" rel="noopener noreferrer"&gt;oauth2-client&lt;/a&gt; for PHP, and verify that what you've built is compatible with the tools other apps will use to integrate with you. As a bonus, it's also the best way to ensure that your code won't show up in the next version of this post.&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>saas</category>
      <category>software</category>
      <category>security</category>
    </item>
    <item>
      <title>SOAP APIs Aren't Scary: What You Should Know Before You Build a SOAP Integration</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Wed, 23 Mar 2022 20:05:09 +0000</pubDate>
      <link>https://dev.to/prismatic/soap-apis-arent-scary-what-you-should-know-before-you-build-a-soap-integration-24ie</link>
      <guid>https://dev.to/prismatic/soap-apis-arent-scary-what-you-should-know-before-you-build-a-soap-integration-24ie</guid>
      <description>&lt;p&gt;Building your first integration with a SOAP-based API can be daunting.&lt;/p&gt;

&lt;p&gt;Recently I’ve helped several companies integrate their apps with third-party apps and services that use SOAP-based APIs. For developers with SOAP experience, the integrations were a breeze. But for the uninitiated, SOAP has a pretty steep learning curve and throws a lot of new terms and acronyms your way: XML, WSDL, envelope, procedure... and a few dozen others.&lt;/p&gt;

&lt;p&gt;Today I want to look at some basic concepts of SOAP, then dive into some tools and resources that make SOAP integrations a lot easier to build (after all, the “S” in SOAP stands for “simple”)!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SOAP?
&lt;/h2&gt;

&lt;p&gt;First, let’s talk about some SOAP basics. If you feel like reading through the entire SOAP spec and its history, you can over at &lt;a href="https://www.w3.org/TR/soap12/"&gt;w3.org&lt;/a&gt;. I’ll try to give the tl;dr here.&lt;/p&gt;

&lt;p&gt;Originally, SOAP stood for “Simple Object Access Protocol.” It was largely built to handle CRUD (create, read, update, delete) operations and it was a &lt;em&gt;protocol&lt;/em&gt; to &lt;em&gt;access&lt;/em&gt; your &lt;em&gt;objects&lt;/em&gt; (like inventory or customer data) in a &lt;em&gt;simple&lt;/em&gt; way. A SOAP API would advertise the types of objects that outside services could fetch and manipulate and would outline exactly how a third party should make CRUD requests.&lt;/p&gt;

&lt;p&gt;SOAP has since dropped that acronym because it does a lot more than object manipulation, but the idea is still the same: it’s a protocol for two systems to communicate with one another in a predictable way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web services and WSDLs
&lt;/h2&gt;

&lt;p&gt;A SOAP API provides a &lt;strong&gt;web service&lt;/strong&gt;. These web services are made up of one or more &lt;strong&gt;procedures&lt;/strong&gt; (called operations). For example, you might create a “weather forecast” web service, and it could include a “get the 10-day forecast” operation, or an “I’m a weather station, and here’s our latest temperature data” operation.&lt;/p&gt;

&lt;p&gt;Requests to each operation take very specific formats for their input, and they have predictable formats for their responses. The “get the 10-day forecast” operation might take a numerical zip code as its input and return an array of floating point temperatures in Fahrenheit that are forecast over the coming ten days for that zip code.&lt;/p&gt;

&lt;p&gt;When SOAP-based web services are published, a summary WSDL (Web Services Description Language) file is published. The WSDL, written out in XML, outlines the various operations that the service offers, as well as what inputs and responses you can expect for each operation.&lt;/p&gt;

&lt;p&gt;Let’s look at a simple WSDL for a rudimentary “calculator service.” The WSDL (&lt;a href="http://www.dneonline.com/calculator.asmx?WSDL"&gt;here&lt;/a&gt;) defines some basic calculator operations (like addition, subtraction, multiplication, and division). This portion of the calculator WSDL declares that the service has an addition “Add” operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;wsdl:operation&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Add"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;wsdl:documentation&lt;/span&gt; &lt;span class="na"&gt;xmlns:wsdl=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.xmlsoap.org/wsdl/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Adds two integers. This is a test WebService. ©DNE Online
 &lt;span class="nt"&gt;&amp;lt;/wsdl:documentation&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;wsdl:input&lt;/span&gt; &lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;"tns:AddSoapIn"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;wsdl:output&lt;/span&gt; &lt;span class="na"&gt;message=&lt;/span&gt;&lt;span class="s"&gt;"tns:AddSoapOut"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/wsdl:operation&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we look further into this XML, we see that the &lt;code&gt;wsdl:input&lt;/code&gt; references a message type &lt;code&gt;tns:AddSoapIn&lt;/code&gt;. Looking a few lines prior where some parameters are declared, we see this section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;wsdl:message&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"AddSoapIn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;wsdl:part&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"parameters"&lt;/span&gt; &lt;span class="na"&gt;element=&lt;/span&gt;&lt;span class="s"&gt;"tns:Add"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/wsdl:message&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That portion of the WSDL references an element &lt;code&gt;tns:Add&lt;/code&gt;, which is found in another previous section of the WSDL. The &lt;code&gt;tns:Add&lt;/code&gt; element defines a complex input type with a couple of integers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;s:element&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Add"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;s:complexType&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;s:sequence&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;s:element&lt;/span&gt; &lt;span class="na"&gt;minOccurs=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;maxOccurs=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"intA"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"s:int"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;s:element&lt;/span&gt; &lt;span class="na"&gt;minOccurs=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;maxOccurs=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"intB"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"s:int"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/s:sequence&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/s:complexType&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/s:element&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, one criticism of SOAP is that it is incredibly verbose - this WSDL is no exception, but in layman’s terms all that the above XML really says is “this service has an &lt;code&gt;Add&lt;/code&gt; operation and it takes two integers, &lt;code&gt;intA&lt;/code&gt; and &lt;code&gt;intB&lt;/code&gt;, as inputs.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming a SOAP API
&lt;/h2&gt;

&lt;p&gt;Okay.... there’s an addition procedure out there that we want to invoke and it takes two integers. Great. How do we go about calling it? SOAP APIs are almost always HTTP-based, so we need to craft an HTTP request from the WSDL we looked at.&lt;/p&gt;

&lt;p&gt;SOAP APIs always communicate with XML, so we’ll start by adding a header that sets our &lt;code&gt;Content-Type&lt;/code&gt; to &lt;code&gt;text/xml; charset=utf-8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The data we send needs to be in XML format, and SOAP wraps data sent back and forth in a SOAP &lt;strong&gt;envelope&lt;/strong&gt; (an envelope defines the message’s structure). If we look at the calculator WSDL again, we can glean the envelope’s structure. Within our SOAP envelope’s “body” we’ll declare what procedure we’re going to run (&lt;code&gt;Add&lt;/code&gt;), and we’ll include values for the inputs that the procedure expects (for &lt;code&gt;intA&lt;/code&gt; and &lt;code&gt;intB&lt;/code&gt;). Our HTTP request will end up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://www.dneonline.com/calculator.asmx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: text/xml; charset=utf-8"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s1"&gt;'&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;soap:Envelope
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="http://www.w3.org/2003/05/soap-envelope"&amp;gt;
 &amp;lt;soap:Body&amp;gt;
    &amp;lt;Add xmlns="http://tempuri.org/"&amp;gt;
     &amp;lt;intA&amp;gt;15&amp;lt;/intA&amp;gt;
     &amp;lt;intB&amp;gt;27&amp;lt;/intB&amp;gt;
    &amp;lt;/Add&amp;gt;
 &amp;lt;/soap:Body&amp;gt;
&amp;lt;/soap:Envelope&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running that HTTP requests from our command line, we get a response that’s also in XML, and also wrapped in an envelope. The response’s body includes the &lt;code&gt;AddResponse&lt;/code&gt; that’s defined in the WSDL, and that has an &lt;code&gt;AddResult&lt;/code&gt; - the sum of the two numbers we sent to the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;soap:Envelope&lt;/span&gt;
 &lt;span class="na"&gt;xmlns:soap=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2003/05/soap-envelope"&lt;/span&gt;
 &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
 &lt;span class="na"&gt;xmlns:xsd=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;soap:Body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;AddResponse&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://tempuri.org/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;AddResult&amp;gt;&lt;/span&gt;42&lt;span class="nt"&gt;&amp;lt;/AddResult&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/AddResponse&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/soap:Body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/soap:Envelope&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phew... that’s a hell of a lot of parsing through a WSDL and monkeying with XML schema just to add two numbers together. It’s nice to have predictable request and response formats, but a missing &lt;code&gt;xmlns&lt;/code&gt; XML attribute or typo’d operation name can result in the SOAP server rejecting your requests. Hand-writing these XML requests is pretty error-prone. Let’s reach for some tools to simplify things.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOAP tooling
&lt;/h2&gt;

&lt;p&gt;Obviously manually parsing a WSDL with your eyes and hand-writing HTTP requests is burdensome and should be avoided at all costs. I recommend reaching for a SOAP tool that parses the WSDL for you and presents you with a list of operations (and their inputs) that you can invoke. There are several great apps out there built for exactly that purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.soapui.org/"&gt;SoapUI&lt;/a&gt; from SmartBear is a popular tool that gives you a great graphical UI for navigating through a WSDL. Popping open any operation gives you a sample request that you can fill in and execute from within their app:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdrZI-gZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zajszjys35d8d4wuskeg.png" alt="Screenshot of SoapUI from SmartBear" width="880" height="538"&gt;
&lt;/li&gt;
&lt;li&gt;Recent versions of the popular HTTP client &lt;a href="https://blog.postman.com/postman-now-supports-wsdl/"&gt;Postman&lt;/a&gt; also support importing a WSDL, and it even provides you with pre-populated sample requests that you can send to the web service from within the app:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hfAAPuuI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6uox1v73pxhr57c0np39.png" alt="Screenshot of Postman SOAP request" width="880" height="553"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These graphical tools are great for &lt;a href="https://prismatic.io/blog/how-to-build-an-integration-to-a-third-party-app/#explore-the-api-with-an-http-client"&gt;exploring a SOAP API&lt;/a&gt;, and I recommend you familiarize yourself with the web service’s operations prior to sitting down and coding up your integration. Once you get a good idea of how the SOAP API works, it’s time to turn to code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing code to interact with SOAP APIs
&lt;/h2&gt;

&lt;p&gt;Manually parsing a WSDL and hand-writing HTTP requests is error-prone and just generally an awful experience. You definitely don’t want to resort to templating out XML with string literals. Let’s avoid that when writing code to interact with a SOAP-based web service.&lt;/p&gt;

&lt;p&gt;Whether you’re using Python, NodeJS, C#, or any other modern programming language, the chances are good that there’s a SOAP library you can leverage to make it simple to invoke web service operations. In NodeJS, for example, you can reach for the aptly named &lt;a href="https://www.npmjs.com/package/soap"&gt;soap&lt;/a&gt; package on NPM.&lt;/p&gt;

&lt;p&gt;This library can take the URL of a WSDL as an argument, and in doing so creates a fully functional HTTP client object that can invoke any of the web service’s operations. We can perform something like &lt;code&gt;client.Add&lt;/code&gt; to invoke the “Add” operation, pass in a couple of input arguments, and the rest (parsing the WSDL, formatting the XML request in an envelope, deserializing the XML response, etc.) is handled for you. With just a half-dozen lines of code (I don’t count parentheses!), we can make a request to the calculator API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;soap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;soap&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsdlUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://www.dneonline.com/calculator.asmx?WSDL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;intA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;intB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;soap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsdlUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`The API returned a sum of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddResult&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response to our request contains an &lt;code&gt;AddResult&lt;/code&gt; property, and our function prints out &lt;code&gt;The API returned a sum of 42&lt;/code&gt;, like we’d expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstracting SOAP Further
&lt;/h2&gt;

&lt;p&gt;If you’re building integrations that connect your own product to other apps your customers use, an embedded integration platform as a service (embedded iPaaS) like &lt;a href="https://prismatic.io/"&gt;Prismatic&lt;/a&gt; can abstract much of the complexity of interacting with SOAP APIs. Embedded integration platforms include low-code integration builders with pre-built connectors for popular SOAP-based apps (like Salesforce), which completely eliminates the need to write code to interact with many SOAP-based APIs.&lt;/p&gt;

&lt;p&gt;For less common SOAP-based APIs that don’t have built-in connectors, they typically offer a SOAP component that abstracts out the complexity of making SOAP requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JQX1AZWX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frw101qga9t0jmdblqo6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JQX1AZWX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frw101qga9t0jmdblqo6.png" alt="Screenshot of Prismatic Built-in SOAP Component" width="880" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In many situations, you can leverage an embedded iPaaS to interact with a SOAP-based API while writing little to no code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Obviously the calculator web service is incredibly simple, but I think it does a good job illustrating how a SOAP web service can appear daunting at first, but with the right tooling it’s relatively clean and simple to build an integration with a SOAP-based API.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>tooling</category>
      <category>api</category>
    </item>
    <item>
      <title>Proxying Github Through a Bastion</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Tue, 22 Feb 2022 15:37:39 +0000</pubDate>
      <link>https://dev.to/taylorreece/proxying-github-through-a-bastion-1m8o</link>
      <guid>https://dev.to/taylorreece/proxying-github-through-a-bastion-1m8o</guid>
      <description>&lt;p&gt;Despite the &lt;a href="https://www.githubstatus.com/"&gt;status page&lt;/a&gt; saying otherwise, GitHub is down for part of the midwest. Though a 4-day weekend would be cool, getting access to code would be even cooler.&lt;/p&gt;

&lt;p&gt;So... let's get to GitHub via a SOCKS proxy!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SOCKS?
&lt;/h2&gt;

&lt;p&gt;SOCKS simply stands for "socket secure".  It's a way by which you can proxy your web traffic on your computer through another system. All of your HTTP and HTTPS requests are piped through an SSH connection, and another system essentially makes connections for you on your behalf.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use SOCKS?
&lt;/h3&gt;

&lt;p&gt;If you're either on a network that's locked down, or your network happens to be suffering an outage (like the midwest GitHub outage right now), but other networks are fine (AWS us-east-1 can hit GitHub right now), it's advantageous to have the other network make connections for you on your behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Make a Connection!
&lt;/h2&gt;

&lt;p&gt;Alright, you're going to need a Linux box sitting somewhere that can access GitHub. I spun up an Ubuntu box in AWS's us-east-1 region, which is not experiencing an outage. I made sure I can SSH into the external box, then I ran this command on my command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -D 1337 -q -C -N -i ~/my-key.pem ubuntu@my-endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;my-key.pem&lt;/code&gt; with your SSH private key, and &lt;code&gt;my-endpoint&lt;/code&gt; with your EC2's endpoint. The &lt;code&gt;1337&lt;/code&gt; can be changed to whatever local port you'd like to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure SOCKS Proxy
&lt;/h2&gt;

&lt;p&gt;Next, on MacOS pop open your network settings, click &lt;strong&gt;Advanced&lt;/strong&gt;, then under the &lt;strong&gt;Proxies&lt;/strong&gt; tab select &lt;strong&gt;SOCKS Proxy&lt;/strong&gt;.  Enter &lt;code&gt;localhost&lt;/code&gt; and the port you chose (like &lt;code&gt;1337&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3u3khGsi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/liadsbz61pjapmamzd48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3u3khGsi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/liadsbz61pjapmamzd48.png" alt="Configure SOCKS proxy" width="880" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Windows has &lt;a href="https://superuser.com/questions/1528185/how-can-i-set-socks-proxy-on-windows"&gt;similar settings&lt;/a&gt;, and if you're on Linux you probably already know what you're doing.&lt;/p&gt;

&lt;p&gt;Head on over to &lt;a href="https://wtfismyip.com/"&gt;WTF is My IP&lt;/a&gt; to verify that your browser is proxying through your EC2, then pop on over to GitHub. Your connection should proxy through AWS, and you should be able to hit the GitHub website!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6DaikFM8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbprvc5zuenjbaitcpjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6DaikFM8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbprvc5zuenjbaitcpjj.png" alt="We have GitHub" width="880" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the GitHub CLI
&lt;/h2&gt;

&lt;p&gt;Your GitHub CLI &lt;em&gt;might&lt;/em&gt; connect at this point. That depends on it it respects your global SOCKS configuration, and if your checkout is via &lt;code&gt;https://&lt;/code&gt; or &lt;code&gt;git://&lt;/code&gt;. You may need to set the &lt;code&gt;http.proxy&lt;/code&gt; config flag, explained on this &lt;a href="https://stackoverflow.com/questions/15227130/using-a-socks-proxy-with-git-for-the-http-transport"&gt;Stack Overflow post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if I use SSH?
&lt;/h3&gt;

&lt;p&gt;If you use SSH to interact with GitHub, rather than HTTPS (which is the smart thing to do) you can pop open your SSH config file (likely at &lt;code&gt;$HOME/.ssh/config&lt;/code&gt;) and throw in this SSH proxy config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host my-proxy
  HostName my-ec2-endpoint
  User ubuntu
  IdentityFile ~/my-ec2-key.pem

Host github.com
  ProxyJump my-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That'll make it so your SSH requests to GitHub proxy through your Bastion in AWS. Make sure you remove this later when you can directly access GitHub!&lt;/p&gt;

</description>
      <category>socks</category>
      <category>networking</category>
      <category>github</category>
    </item>
    <item>
      <title>Building an Integration to Another App: How to Get Started</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Wed, 02 Feb 2022 16:20:51 +0000</pubDate>
      <link>https://dev.to/prismatic/building-an-integration-to-another-app-how-to-get-started-10nk</link>
      <guid>https://dev.to/prismatic/building-an-integration-to-another-app-how-to-get-started-10nk</guid>
      <description>&lt;p&gt;[&lt;em&gt;ding&lt;/em&gt; 🔔] goes your email notification. You’ve just been assigned a new ticket. The subject line reads “Integrate our app with Acme CRM.” The ticket’s description is pretty sparse - just a few bullet-points saying things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sync users’ appointments in Acme with our calendar app&lt;/li&gt;
&lt;li&gt;Track the number of email and phone touch-points users have in Acme on our dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You expected this. A lot of the businesses that use your software also use Acme CRM, and you’ve been hearing from your sales and support teams that customers keep asking for their Acme data to sync with your app.&lt;/p&gt;

&lt;p&gt;So.... it’s up to you to build the integration with Acme. Where do you even begin?&lt;/p&gt;

&lt;p&gt;I know I’m always inclined to fire up my IDE and dive right in to code, but that always results in misunderstanding expectations, missing details, and having to redo work.&lt;/p&gt;

&lt;p&gt;Let’s talk about the stuff you should do &lt;em&gt;before&lt;/em&gt; you start coding. Let’s think through the questions you should ask, requirements you should clarify, and tools you can reach for to get a feel for how the integration is going to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gather requirements for your integration project
&lt;/h2&gt;

&lt;p&gt;In an ideal world, the person requesting the integration would provide you with a specification that details what events trigger the integration to run, what type of data gets sent over, and how the data should look when it arrives at its destination. The world’s rarely ideal, though, so you’ll probably have to go digging for clearer integration requirements.&lt;/p&gt;

&lt;p&gt;It’s incredibly helpful to have a series of “When &lt;em&gt;foo&lt;/em&gt; then &lt;em&gt;bar&lt;/em&gt;” statements. For example, “When a user in Acme CRM sets an appointment with a customer, a calendar event is created in our app that contains the customer’s name, phone number, email address, and time of the appointment.” Bonus points if you can tie each step to relevant API documentation!&lt;/p&gt;

&lt;p&gt;As you think through the “when &lt;em&gt;foo&lt;/em&gt; then &lt;em&gt;bar&lt;/em&gt;” statements, there are several companion questions you should be asking with regards to how data should flow from one system to another:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What triggers an integration to run? Does Acme support events like webhooks (where they send data whenever it’s been changed) or are you expected to fetch data on a schedule?&lt;/li&gt;
&lt;li&gt;Is this a one-way or two-way integration (that is, will Acme just send data to your app, or does your app also need to send data back to Acme)?&lt;/li&gt;
&lt;li&gt;What’s the format of the data the source system is sending? JSON? XML? CSV?&lt;/li&gt;
&lt;li&gt;What information is included in the payload that’s sent your way?&lt;/li&gt;
&lt;li&gt;What format of data is the destination system expecting? You may need to fetch more information from other third-parties or Acme’s other API endpoints to satisfy the destination’s requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After you have a sense of how data will flow, ask yourself what things could go wrong, and clarify how your integration needs to behave in these common situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How should you deal with data “collisions” where the same data gets sent over twice? Should you ignore the second request, create a second record, or update (”upsert”) the existing data?&lt;/li&gt;
&lt;li&gt;What should happen if incomplete or “bad” data is shipped (a calendar event that’s missing date, for example)? Do you need a “dead letter queue” to store data that causes your integration to error out so your devs or support folks can examine the bad data later?&lt;/li&gt;
&lt;li&gt;What happens if either API is down? Is your integration expected to hold on to data and retry later?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, figure out how configurable you need this integration to be. (Occasionally, I’ve needed to build “one-off” integrations for a single customer. Far more often, I’ve needed to build reusable integrations that can behave differently from customer to customer and handle each customer’s credentials for the third-party app.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do users authenticate with each system? Do users have their own credentials to Acme, or some shared key? Where are you going to store those secrets securely?&lt;/li&gt;
&lt;li&gt;What things are going to be different between customers you deploy this integration to? Do customers have different API endpoints, or data mapping requirements?&lt;/li&gt;
&lt;li&gt;What all needs to be configurable to make it so you can deploy the same integration to multiple customers?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, figure out deployment and support concerns. If your company has several app integrations already, you may already have answers to these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How will your customers activate and configure the integration? Do you need to build UX for this?&lt;/li&gt;
&lt;li&gt;What are the expectations for logging, monitoring and alerting? Where should the integration’s logs be sent, and under what conditions should someone be alerted if the integration runs into a problem?&lt;/li&gt;
&lt;li&gt;Where is the integration itself going to live? Will you need to build custom infrastructure to host this integration?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can see, a quick bullet point spec like “Import users’ appointments into the our calendar” seems simple at first, but there’s a ton of things to think through if you want to build, deploy and support your integration thoroughly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get familiar with how the third-party app works
&lt;/h2&gt;

&lt;p&gt;Once you’ve thought through all of the questions above and have a solid understanding of the task at hand, it’s time for you to familiarize yourself with the third-party app(s) involved. I recommend logging in to Acme CRM like an end user would. Figure out what exactly it means to “set an appointment” or “log a phone call with a customer” in the context of Acme. This should give you a sense of how different objects that the application stores are related.&lt;/p&gt;

&lt;p&gt;If you don’t have an Acme CRM account, ask for one. It’s mutually beneficial for both your company and Acme to have an integration between your systems, and most reasonable companies are willing to provide a free developer account or “sandbox” environment where you can toy around with dummy data, without needing to access your users’ real Acme data.&lt;/p&gt;

&lt;p&gt;You may be provided written information about how the other application will provide data to you, but I recommend that you insist on getting a real sandbox environment that at least sends fake data in the format the third party says they use. I’ve been around for plenty of integrations where a third party claims they’ll send XML in thus-and-such a format using REST calls, but informs you after you build your integration that they decided to write out JSON messages to a message broker (like Kafka or Amazon SNS) instead. &lt;em&gt;You can just change your code quick to subscribe to an SNS feed, right?&lt;/em&gt; Testing with an actual system is far better than mocking out fake APIs and message streams yourself.&lt;/p&gt;

&lt;p&gt;It sounds silly, but also make sure to log in to your own app as well to verify that you know what it means to “create a calendar event” or “update the dashboard.” Verify with the people requesting the integration that you understand what actions should trigger the integration, and where the data should be displayed when it arrives at its destination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the API with an HTTP client
&lt;/h2&gt;

&lt;p&gt;You’re ready to start fiddling with APIs, but it’s not quite time to code quite yet. Take a look at Acme’s developer docs. If they have an OpenAPI/Swagger or WSDL spec that defines all for their API endpoints, you’re in luck! Figure out how their auth flow works, what data their webhooks send, and what data is available for you to query. If you’re really lucky, Acme will provide a client library you can use when it’s time to start coding (but don’t get your hopes up!).&lt;/p&gt;

&lt;p&gt;Fire up your favorite HTTP client (I personally like &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;), and try to make some simple authenticated GET and POST requests against the Acme CRM sandbox you have access to. Verify that the third-party docs are correct, and that you can, indeed, fetch data from their appointment API endpoint. Double-check that the payload you receive has all the information you need to map data over to your app.&lt;/p&gt;

&lt;p&gt;Once you have requests to Acme CRM working in Postman (or your favorite HTTP client), finally turn to code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time to build!
&lt;/h2&gt;

&lt;p&gt;Now that you have a good idea of the shape of data that comes in, where additional data should be fetched from, how it should be modified and where it should wind up, it’s time to start building your integration.&lt;/p&gt;

&lt;p&gt;There’s still a lot to do - you need to write your code, make it configurable enough to handle differences between customers, deploy it, monitor and support it, make the integration available within your product, etc., but asking the right questions and taking the proper steps beforehand can make all that go faster and keep rework to a minimum.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Taylor
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/taylor-reece/"&gt;Taylor&lt;/a&gt; is a developer advocate at &lt;a href="https://prismatic.io/"&gt;Prismatic&lt;/a&gt; (an integration platform for B2B software companies) where he helps customers build integrations connecting their products to their customers’ other apps. If you’d like to chat with Taylor or the folks at Prismatic, please &lt;a href="https://prismatic.io/contact/"&gt;reach out&lt;/a&gt; - we’d love to chat about the integrations you need to build for your app!&lt;/p&gt;

</description>
      <category>integration</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Built-in Node Functions Can Be Overridden Between Lambda Runs</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Fri, 23 Apr 2021 15:31:43 +0000</pubDate>
      <link>https://dev.to/taylorreece/built-in-node-functions-can-be-overridden-between-lambda-runs-ge6</link>
      <guid>https://dev.to/taylorreece/built-in-node-functions-can-be-overridden-between-lambda-runs-ge6</guid>
      <description>&lt;p&gt;A couple days ago I wrote up a post on &lt;a href="https://dev.to/prismatic/why-we-moved-from-lambda-to-ecs-4m96"&gt;Why We Moved From Lambda to ECS&lt;/a&gt;. One thing I was startled by was how a "warm" Lambda can behave between invocations. I thought I'd expand on that issue briefly with a simple example:&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Thought Lambda Worked
&lt;/h2&gt;

&lt;p&gt;My (incorrect) understanding of Lambda was that each invocation of a Lambda was isolated. I figured, besides the startup code that runs once when a "cold" Lambda gets warmed, that Lambda executions were stateless and wouldn't affect one another.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Actually Works
&lt;/h2&gt;

&lt;p&gt;In reality, two Lambdas that run in parallel are perfectly isolated. However, if a Lambda has run and is sitting around "warm", subsequent executions using that Lambda might be affected by a previous execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Overriding console.log()
&lt;/h3&gt;

&lt;p&gt;Suppose, for example, you have a simple Lambda with this code in &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your message has been hijacked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time this Lambda is invoked, you see a happy "Hello, World!" logged out.&lt;/p&gt;

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

&lt;p&gt;If you test the Lambda again while the Lambda is still warm, though, the first invocation overrode the &lt;code&gt;console.log()&lt;/code&gt; function, so you end up seeing an error, "Your message has been hijacked".&lt;/p&gt;

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

&lt;p&gt;You can imagine how someone might exploit this issue if they can invoke their own code in a Lambda that others then use.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why We Moved From Lambda to ECS</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Wed, 21 Apr 2021 14:46:26 +0000</pubDate>
      <link>https://dev.to/prismatic/why-we-moved-from-lambda-to-ecs-4m96</link>
      <guid>https://dev.to/prismatic/why-we-moved-from-lambda-to-ecs-4m96</guid>
      <description>&lt;p&gt;After many months of development, my team just announced the general availability of our platform. That milestone seems like a perfect opportunity to look back and reflect on how the infrastructure that supports Prismatic has evolved over time. (Spoiler: We ended up moving our most important microservice from Lambda to ECS.)&lt;/p&gt;

&lt;p&gt;In this post I'll dive into what went well in Lambda, what challenges we faced, and why we eventually made the decision to migrate some services from Lambda to AWS Elastic Container Service (ECS).&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem are We Solving?
&lt;/h2&gt;

&lt;p&gt;For some quick context, our product is an integration platform for B2B software companies. That is, we help software companies build integrations and deploy those integrations to their customers. A simple integration might look something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Pull down an XML document from Dropbox.&lt;/li&gt;
&lt;li&gt;Step 2: Process the XML with some custom JavaScript code.&lt;/li&gt;
&lt;li&gt;Step 3: Use some stored credentials to post the processed data to a third-party API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our users can configure integrations to run on a schedule, or they can trigger them via a webhook, and our platform takes care of running, logging, and monitoring the integrations (and a whole bunch of other things).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Early Days
&lt;/h2&gt;

&lt;p&gt;The first incarnation of Prismatic used &lt;a href="https://localstack.cloud/"&gt;LocalStack&lt;/a&gt;. We knew that we wanted to eventually host Prismatic in AWS (with the possibility of moving to Azure, GCP, etc. as needed), so the ability to spin up our platform locally to simulate AWS was appealing. The LocalStack service that approximates AWS Lambda was easy to iterate on, and ran without any major hiccups. It gave us a great development feedback loop, so we could prototype and test very quickly.&lt;/p&gt;

&lt;p&gt;We used Lambda to execute each "step" of an integration, and steps leveraged SQS to pass data and trigger the next step. So, an integration execution would look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a Dropbox "fetch a file" action to grab an XML file.&lt;/li&gt;
&lt;li&gt;Save the contents of that XML file to SQS, trigger the next step.&lt;/li&gt;
&lt;li&gt;Run a customer's custom JavaScript code to process the XML.&lt;/li&gt;
&lt;li&gt;Save the resulting transformed data to SQS, trigger the next step.&lt;/li&gt;
&lt;li&gt;Run an action to post the processed data to a third-party API.&lt;/li&gt;
&lt;li&gt;Save the results of the final step, trigger the end of the integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within LocalStack, this was a very quick process. We could define a 6-step integration, run it, and see results within a couple of seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Migration to Real AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Once we had a proof of concept working, we devoted some time to moving Prismatic to an actual production environment, with real Lambdas, queues, databases, etc. We were still a small team, and we didn't want to dedicate a ton of time to DevOps-y, infrastructure problems yet. We wanted to dedicate most of our time to our core product, and Lambda let us do just that.&lt;/p&gt;

&lt;p&gt;Lambda was attractive to us for a number of reasons. We didn't need to worry about CPU or memory allocation, server monitoring, or autoscaling; that's all built-in. We were able to throw .zip files full of JavaScript code at Lambda, and AWS took care of the rest. Lambda let us compartmentalize our code into a series of microservices (a service for logging, a service for OAuth key renewals, a service for SMS/email alerting if integrations error out, etc.), so we could keep a good mental map of what code is responsible for doing what task. Costs were pretty reasonable, too - you just pay for compute time, so rather than running servers 24/7, we just paid when our prototypes were executing something.&lt;/p&gt;

&lt;p&gt;After a few days monkeying with &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt;, we had our second incarnation of Prismatic in AWS. Our integration runners ran on real Lambda, and were triggered via SQS. This is the point at which we started running into performance issues with our integration runners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Lambda Didn't Work for Us
&lt;/h2&gt;

&lt;p&gt;We had a number of issues, ranging from speed to SQS size limits and lack of process isolation in Lambda, that caused us to reconsider its effectiveness as our integration runner. Let's talk about each of those issues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; Remember the 6-step integration that I said took a couple of seconds to run within LocalStack? It took a full minute using real Lambda and AWS. The actual Lambda invocations were quick - usually a few milliseconds. The writing of step results to SQS and subsequent execution of the next step, though, ended up taking multiple seconds every step. For more complex integrations, like ones that looped over 500 files, that was a show-stopper - who wants their integrations to take minutes (hours?) to complete?&lt;/p&gt;

&lt;p&gt;We tried a number of things to get our Lambda invocations to go faster. We followed guides to keep a number of Lambda instances "warm", and we cranked up the number of vCPUs powering our Lambdas to the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/12/aws-lambda-supports-10gb-memory-6-vcpu-cores-lambda-functions/"&gt;highest we could at the time&lt;/a&gt; (6 vCPUs / 10GB RAM), but those things only shaved single digit percentages off of our integration run times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQS Size Limits&lt;/strong&gt;. SQS &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html"&gt;limits message size&lt;/a&gt; to &lt;em&gt;256 kilobytes&lt;/em&gt;. The amount of data being passed between steps of an integration often exceeded that size (after all, it's totally reasonable for an integration developer to pull down a multiple megabyte JSON file to process). We were able to work around this size limitation - the recommended solution that we followed was to write out payloads to S3 and pass references to S3 objects via SQS - but this extra API call to S3 only compounded our slowness issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Process Isolation&lt;/strong&gt;. This was the issue that surprised me the most. At first, AWS Lambda seems appealing as a stateless compute engine - run a job, exit, run another job, etc - scale horizontally as needed. We naively assumed that Lambda invocations were isolated from one another, but that turned out to only be half true. Concurrent invocations are isolated (they run in distinct containers within Lambda). However, subsequent invocations reuse previous "warm" environments, so an integration runner might inherit a "dirty" environment from a previous integration run. That's especially a problem if you let customers write their own code, like we do for our customers' integrations.&lt;/p&gt;

&lt;p&gt;It turns out that if one customer writes some bad code into their integration - something like this, &lt;code&gt;global.XMLHttpRequest = null;&lt;/code&gt;, then subsequent integration runs on that same Lambda that depend on the &lt;code&gt;XMLHttpRequest&lt;/code&gt; library error out. This is a big deal, since one customer could break something like &lt;a href="https://www.npmjs.com/package/axios"&gt;axios&lt;/a&gt; for another customer. A customer could even be malicious and execute something like &lt;code&gt;global.console.log = (msg) =&amp;gt; { nefariousCode(); }&lt;/code&gt;, and other integrations that execute on that same Lambda will run &lt;code&gt;nefariousCode()&lt;/code&gt; whenever they invoke &lt;code&gt;console.log()&lt;/code&gt;. Yikes!&lt;/p&gt;

&lt;p&gt;We tried a few things to get around this issue of shared execution space. We toyed with forcing our Lambdas to cold-start every time (which was a terrible idea for obvious reasons), and we tried spinning up distinct Node processes within &lt;a href="https://www.thegeekdiary.com/understanding-chroot-jail/"&gt;chroot jails&lt;/a&gt;. Neither option panned out - spinning up child Node processes in a Lambda took 3-5 seconds and partially defeated the purpose of being in Lambda in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Move to ECS
&lt;/h2&gt;

&lt;p&gt;Lambda had served us well with development - we were able to iterate quickly and get a prototype out the door, but with the myriad issues we faced in Lambda we decided to bite the bullet and dedicate some dev time to cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Our team got to work expanding our existing Terraform scripts, and moved our integration runner to AWS Elastic Container Service (ECS). Within an ECS container we could easily (and quickly!) &lt;code&gt;chroot&lt;/code&gt; and isolate Node processes from one another, solving the process isolation issues we were seeing in Lambda. To get around the SQS size limit issues we faced, we swapped in a Redis-backed queuing service. We had to reinvent some wheels that Lambda had given us for free - like logging, autoscaling, and health checks - but in the end we had our 6-step test integration back to running in under 2 seconds.&lt;/p&gt;

&lt;p&gt;Now, ECS hasn't been perfect - there are are some trade-offs. For one, ECS doesn't seem to autoscale as quickly as Lambda. A "scale up" seems to take about a minute or so between API call and &lt;a href="https://aws.amazon.com/fargate/"&gt;AWS Fargate&lt;/a&gt; pulling down and initializing a container that's ready to accept jobs. We had to pull one of our devs off of product development to work on cloud infrastructure, and there's a ton more to juggle with regards to CPU and memory usage, autoscaling rules, and monitoring, but at this point in product development the pains are worth the gains to give our customers a speedy integration runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Remained in Lambda
&lt;/h2&gt;

&lt;p&gt;We didn't move all of our microservices out of Lambda - plenty still remain in the serverless ecosystem and will for the foreseeable future. Our integration runner didn't fit Lambda well, but there are other tasks for which Lambda seems like the clear choice. We kept all important integration services that aren't critical to the actual execution of the integration in Lambda. Those include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A logger service that pulls logs from ECS and sends them to &lt;a href="https://www.datadoghq.com/"&gt;DataDog&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A service that writes metadata about integration executions to a PostgreSQL database.&lt;/li&gt;
&lt;li&gt;A service that tracks and queues scheduled integrations.&lt;/li&gt;
&lt;li&gt;An alerting service that sends SMS or email notifications to users if their integrations error.&lt;/li&gt;
&lt;li&gt;An authorization service that renews customers' OAuth 2.0 keys for third party services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We didn't want any of these services to block execution of an integration, and for all of them it's fine if they take an additional second or two to run, so services like those fit Lambda perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Our infrastructure definitely changed over time, but I think the decisions we made along the way were the right ones: LocalStack's "Lambda" service let us develop and iterate very quickly, and our first deployment into AWS was simple enough that our small dev team could Terraform our infrastructure without losing a ton of dev hours to it.&lt;/p&gt;

&lt;p&gt;Lambda seemed like an attractive solution for hosting and scaling our microservices, and for many of them, especially asynchronous services that might take a second or two to run, it still remains the correct choice. For our integration runner, though, we learned that the size, speed, and process isolation limitations of Lambda made ECS a better option, and it was worth the dev time it took to create an ECS deployment for that particular service.&lt;/p&gt;

&lt;p&gt;Lambda let us concentrate on product development early on, and when the time was right the transition to ECS was a fairly smooth one. Even with the issues we faced in Lambda, I'm glad we took the path we did.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>serverless</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to Connect to Private AWS Resources with SSH Tunnels and Bastion Hosts</title>
      <dc:creator>Taylor Reece</dc:creator>
      <pubDate>Thu, 17 Dec 2020 15:03:29 +0000</pubDate>
      <link>https://dev.to/prismatic/how-to-connect-to-private-aws-resources-with-ssh-tunnels-and-bastion-hosts-2gma</link>
      <guid>https://dev.to/prismatic/how-to-connect-to-private-aws-resources-with-ssh-tunnels-and-bastion-hosts-2gma</guid>
      <description>&lt;h3&gt;
  
  
  The Problem with Publicly Accessible AWS Resources
&lt;/h3&gt;

&lt;p&gt;When you first develop infrastructure for a new project, you naturally optimize for rapid development. You want to get something - anything - out the door, and you therefore want to be able to write code and debug issues quickly.&lt;/p&gt;

&lt;p&gt;Because of that, it's awfully tempting to spin up servers and databases in public subnets so that you can readily connect to them for debugging sessions. It's nice to be able to &lt;code&gt;ssh my-user@my-web-server&lt;/code&gt; to do some live code debugging, or &lt;code&gt;psql -U my-user -h my-database-instance&lt;/code&gt; to assess the current state of your database.&lt;/p&gt;

&lt;p&gt;Sure, AWS allows you to do that. You &lt;em&gt;can&lt;/em&gt; expose SSH on your webservers and you &lt;em&gt;can&lt;/em&gt; expose your AWS Relational Database Service (RDS) and Redis/ElastiCache servers to the internet for easy access. But should you? Exposing databases and servers publicly yields a pretty obvious security issue: your sensitive data is directly accessible to anyone on the internet.&lt;/p&gt;

&lt;p&gt;You might argue "it's fine; we lock down our VPC security groups so that only people from our office IP can access our EC2, RDS, or ElastiCache instances. Other people on the internet are blocked because of their IP addresses". While that's true - locking down your resources this way is better than nothing - this strategy yields two pitfalls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Allowing anyone from your office IP address to access your AWS resources is still a security issue. Should guests on your WiFi, or John the junior developer really have access to production databases? By locking down your resources by public IP, it's pretty hard to discriminate between DevOps users and the rest of your organization - everyone likely uses the same public IP address. Plus, as your organization grows, more attack vectors are created. A single compromised user on your network can wreak havoc on your production systems.&lt;/li&gt;
&lt;li&gt;Your remote workers are going to struggle to hit resources. If your employees are working from home (which, in this COVID-riddled year, they very likely are), you will either need your employees to maintain a VPN connection to your office, or you will need to maintain a whitelist of all of your employees' home IP addresses. If your employees happen respond to an outage from an airport or coffee shop, they might spend minutes (hours?) manually monkeying with VPC security groups before they can even get access to address the outage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLuff7ml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d4amm6x5v8iarsq866rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLuff7ml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d4amm6x5v8iarsq866rn.png" alt="Illustration showing a public subnet in the AWS Cloud" width="609" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a problem that I've had to solve everywhere that I've worked, so I imagine it's a problem others have had to face, too. Let's look at how to do it right, optimizing for both security and accessibility at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter AWS Systems Manager
&lt;/h3&gt;

&lt;p&gt;Let's suppose you've followed security protocols, and have placed your various AWS resources (EC2, RDS, ElastiCache, etc.) into private subnets and removed public access to them. How can you securely grant access to members on your team (like your senior devs and DevOps) who need them? AWS provides &lt;a href="https://aws.amazon.com/systems-manager/"&gt;Systems Manager&lt;/a&gt; to let you inspect and access your AWS resources - even those residing in private subnets. With Systems Manager, users exchange their AWS credentials for temporary shell access to EC2s. They can get that access both on the CLI, and through the AWS web console.&lt;/p&gt;

&lt;p&gt;To enable access to your EC2s, simply install the &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html"&gt;AWS SSM agent&lt;/a&gt; onto the servers that you spin up. &lt;em&gt;What's the deal with the extraneous "S" in "SSM", you might ask? It's historical - Systems Manager used to be called "Simple Systems Manager" and the extra "S" stuck - just go with it.&lt;/em&gt; Good news - if you're building your servers off of Amazon Linux, macOS, Ubuntu Server, or some Amazon-provided Windows Server images, the SSM agent is already installed for you - you just need to turn on a machine and grant it a specific role defined in AWS's &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have an EC2 with the SSM agent installed, you can open a shell into your instance with the &lt;code&gt;aws&lt;/code&gt; CLI tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ssm start-session &lt;span class="nt"&gt;--target&lt;/span&gt; i-0b6c737cc21dc01a9

Starting session with SessionId: treece-0bf6ff366c16d651f
sh-4.2&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;ssm-user
sh-4.2&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/system-release
Amazon Linux release 2 &lt;span class="o"&gt;(&lt;/span&gt;Karoo&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jkme61I2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q7esvd4p2omsptgwl17r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jkme61I2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/q7esvd4p2omsptgwl17r.png" alt="Illustration showing a private subnet in the AWS Cloud" width="635" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you have the SSM agent installed, you don't need to worry about juggling IP whitelists. Your systems management teams can access resources from anywhere (the office, at home, or even a coffee shop) using their AWS credentials. And, without AWS credentials John the junior dev has no access to production databases. Sorry, John!&lt;/p&gt;

&lt;h3&gt;
  
  
  What About Accessing RDS or ElastiCache?
&lt;/h3&gt;

&lt;p&gt;With AWS Systems Manager you can remote into EC2s, but what about databases like PostgreSQL/RDS or Redis/ElastiCache? AWS doesn't allow you to directly SSH into the systems running RDS or ElastiCache. Instead, I suggest spinning up a minimal EC2 instance called a &lt;strong&gt;bastion&lt;/strong&gt; in your VPC that you can remote into with Systems Manager. You can remote into the &lt;strong&gt;bastion,&lt;/strong&gt; and once there you can access your databases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5aBsbSzV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2id2iwij3dpctm45c1x0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5aBsbSzV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2id2iwij3dpctm45c1x0.png" alt="Illustration showing a bastion connecting to a private subnet in the AWS Cloud" width="610" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure that you assign your bastion a VPC security group, and create ingress rules so that your RDS and ElastiCache security groups allow access from your bastion security group. Once you have all that set up, you can access your databases from the command line on the bastion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ssm start-session &lt;span class="nt"&gt;--target&lt;/span&gt; i-0b6c737cc21dc01a9

Starting session with SessionId: treece-048a3bf2269ad7caf
sh-4.2&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; 10.0.2.88 &lt;span class="nt"&gt;-U&lt;/span&gt; testuser postgres
Password &lt;span class="k"&gt;for &lt;/span&gt;user testuser:
SSL connection &lt;span class="o"&gt;(&lt;/span&gt;cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256&lt;span class="o"&gt;)&lt;/span&gt;
Type &lt;span class="s2"&gt;"help"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;help.

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; CREATE TABLE &lt;span class="nb"&gt;users&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id &lt;/span&gt;INT, email VARCHAR&lt;span class="o"&gt;(&lt;/span&gt;40&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
CREATE TABLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might ask, "can't I just spin up a bastion in a public subnet, and SSH into it?" Yes and no. Yes, you can totally do that. No, it's not a good idea - that opens up the same security vulnerabilities that leaving your servers and databases in public subnets does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tunnels... Tunnels Everywhere
&lt;/h3&gt;

&lt;p&gt;At this point, we have options for remoting in to EC2s directly through Systems Manager, and we can access our databases indirectly via a bastion EC2. The last thing I want to look at is leveraging SSH tunnels through the bastion to access database resources as though they're locally accessible. That way, you can connect your favorite database GUI, like &lt;a href="https://www.pgadmin.org/"&gt;pgAdmin&lt;/a&gt; to your RDS instances.&lt;/p&gt;

&lt;p&gt;First, we need to get an SSH public key into our bastion. I'll assume you already have a key in &lt;code&gt;$HOME/.ssh/id_rsa.pub&lt;/code&gt;. We'll use the &lt;code&gt;aws&lt;/code&gt; CLI to temporarily load up that key for user &lt;code&gt;ssm-user&lt;/code&gt; into our bastion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;aws ec2-instance-connect &lt;span class="se"&gt;\&lt;/span&gt;
    send-ssh-public-key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; us-east-1a &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--instance-id&lt;/span&gt; i-0b6c737cc21dc01a9 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--instance-os-user&lt;/span&gt; ssm-user &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--ssh-public-key&lt;/span&gt; file://&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.ssh/id_rsa.pub
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"RequestId"&lt;/span&gt;: &lt;span class="s2"&gt;"f8f3db2a-3107-4c0a-ae3d-94e2d7fff620"&lt;/span&gt;,
    &lt;span class="s2"&gt;"Success"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I'm going to modify my laptop's &lt;code&gt;$HOME/.ssh/config&lt;/code&gt; file so that servers starting with &lt;code&gt;i-&lt;/code&gt;will proxy their connections through the &lt;code&gt;aws ssm start-session&lt;/code&gt; command we used earlier. I'll add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;host i-&lt;span class="k"&gt;*&lt;/span&gt;
    ProxyCommand sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I can simply &lt;code&gt;ssh ssm-user@INSTANCE-ID&lt;/code&gt; to access my instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ssm-user@i-0b6c737cc21dc01a9
Last login: Tue Dec 15 20:27:34 2020 from localhost

       __|  __|_  &lt;span class="o"&gt;)&lt;/span&gt;
       _|  &lt;span class="o"&gt;(&lt;/span&gt;     /   Amazon Linux 2 AMI
      ___|&lt;span class="se"&gt;\_&lt;/span&gt;__|___|

https://aws.amazon.com/amazon-linux-2/
10 package&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; needed &lt;span class="k"&gt;for &lt;/span&gt;security, out of 22 available
Run &lt;span class="s2"&gt;"sudo yum update"&lt;/span&gt; to apply all updates.
&lt;span class="o"&gt;[&lt;/span&gt;ssm-user@ip-10-0-0-71 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls
&lt;/span&gt;file.txt system.log
&lt;span class="o"&gt;[&lt;/span&gt;ssm-user@ip-10-0-0-71 ~]&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that I have SSH access to my bastion (even though it's in a private network that's not accessible from the internet!) I can spin up SSH tunnels like I would with any other SSH connection. To create a tunnel to my RDS instance, for example, I can simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ssm-user@i-0b6c737cc21dc01a9 &lt;span class="nt"&gt;-NL&lt;/span&gt; 5000:10.0.2.88:5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then from a separate shell I can access my database "locally" on port 5000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 5000 &lt;span class="nt"&gt;-U&lt;/span&gt; testuser postgres
Password &lt;span class="k"&gt;for &lt;/span&gt;user testuser:
SSL connection &lt;span class="o"&gt;(&lt;/span&gt;protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off&lt;span class="o"&gt;)&lt;/span&gt;
Type &lt;span class="s2"&gt;"help"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;help.

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="se"&gt;\d&lt;/span&gt;t
         List of relations
 Schema | Name  | Type  |  Owner
&lt;span class="nt"&gt;--------&lt;/span&gt;+-------+-------+----------
 public | &lt;span class="nb"&gt;users&lt;/span&gt; | table | testuser
&lt;span class="o"&gt;(&lt;/span&gt;1 row&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, huh? Using tunnels through your bastion you can access any resources that reside in your private network without needing to expose them to hooligans on the internet... or John the junior dev.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>database</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
