<?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>Forem: Jared Donboch</title>
    <description>The latest articles on Forem by Jared Donboch (@jdonboch).</description>
    <link>https://forem.com/jdonboch</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%2F600488%2F368da4a2-528e-4ce0-a7da-57ff3d7428e3.png</url>
      <title>Forem: Jared Donboch</title>
      <link>https://forem.com/jdonboch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jdonboch"/>
    <language>en</language>
    <item>
      <title>Restricting Unauthenticated  AWS Access by Referer and IP, similar to Google API Keys</title>
      <dc:creator>Jared Donboch</dc:creator>
      <pubDate>Sat, 24 Jul 2021 03:08:22 +0000</pubDate>
      <link>https://forem.com/aws-builders/restricting-unauthenticated-aws-access-by-referer-and-ip-similar-to-google-api-keys-1118</link>
      <guid>https://forem.com/aws-builders/restricting-unauthenticated-aws-access-by-referer-and-ip-similar-to-google-api-keys-1118</guid>
      <description>&lt;p&gt;For anyone who has used Google's JavaScript Map APIs, you know you use an API key to authenticate but you &lt;a href="https://developers.google.com/maps/api-security-best-practices"&gt;can lock down the API key so that only requests from certain referers or IPs are accepted&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In experimenting with &lt;a href="https://dev.to/aws-builders/aws-location-service-where-my-aws-community-builders-at-e02"&gt;creating a simple web-based map for the AWS Builders Community using the new AWS Location Service&lt;/a&gt;, it wasn't intuitive to me how to lock down the &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/authenticating-using-cognito.html"&gt;unauthenticated access provided by the Cognito Identity Pool as described in the AWS Location Service developer guide&lt;/a&gt; and I couldn't find any advice in the developer guides.&lt;/p&gt;

&lt;p&gt;Andrew Johnson pointed me in the right direction and there are conditions that can be added to the Cognito Identity Pool unauthenticated role for both referer, IP addresses, and &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html"&gt;many others&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;I would hope the Amazon Location Service Developer Guide is eventually updated with this advice as this should definitely be a best practice similar to what is recommended for Google Maps.&lt;/p&gt;

&lt;p&gt;That being said, this does not prevent a sophisticated user from spoofing the referer and/or IP and any anonymous access should be minimized to the absolute least privilege.  Here is the warning from the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html"&gt;AWS conditions page&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[The aws:Referer] key should be used carefully. It is dangerous to include a publicly known referer header value. Unauthorized parties can use modified or custom browsers to provide any aws:referer value that they choose. As a result, aws:referer should not be used to prevent unauthorized parties from making direct AWS requests. It is offered only to allow customers to protect their digital content, such as content stored in Amazon S3, from being referenced on unauthorized third-party sites.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check out the examples below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restricting by referer
&lt;/h3&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MapsReadOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"geo:GetMapStyleDescriptor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapGlyphs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapSprites"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapTile"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:geo:us-west-2:xxxxxxx:map/my-map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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;"StringLike"&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;"aws:Referer"&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="s2"&gt;"http://www.example.com/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="s2"&gt;"http://example.com/*"&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;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="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;h3&gt;
  
  
  Restricting by IP
&lt;/h3&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MapsReadOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"geo:GetMapStyleDescriptor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapGlyphs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapSprites"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"geo:GetMapTile"&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;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:geo:us-west-2:xxxxxxx:map/my-map"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&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;"IpAddress"&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;"aws:SourceIp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123.45.67.89"&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;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="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;I've tried this out and it works great and have since added it to my &lt;a href="http://acbmap.humbleg.com/aws/"&gt;public map project(s)&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Let me know if you have any other best practices for least privilege permissions for public/unauthenticated users.&lt;/p&gt;

&lt;p&gt;Want more?  &lt;a href="https://twitter.com/JDonboch"&gt;Follow me on Twitter&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/jareddonboch/"&gt;connect with me on Linked In&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cognito</category>
      <category>googlecloud</category>
      <category>maps</category>
    </item>
    <item>
      <title>AWS Location Service: Where my AWS Community Builders at?</title>
      <dc:creator>Jared Donboch</dc:creator>
      <pubDate>Fri, 11 Jun 2021 20:46:06 +0000</pubDate>
      <link>https://forem.com/aws-builders/aws-location-service-where-my-aws-community-builders-at-e02</link>
      <guid>https://forem.com/aws-builders/aws-location-service-where-my-aws-community-builders-at-e02</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR;&lt;/strong&gt; I built a map of AWS Community Builders using AWS Location Services.  &lt;a href="https://acbmap.humbleg.com/aws/"&gt;Click here for the map&lt;/a&gt;, &lt;a href="https://github.com/jdonboch/acbmap-aws"&gt;click here for the code&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;I was recently accepted into the &lt;a href="https://aws.amazon.com/developer/community/community-builders/"&gt;AWS Community Builders program&lt;/a&gt; and have been blown away by how geographically diverse the group is.  The program is split up into different technology-based cohorts--although this is very loose coupling and members are free to join discussions and sessions for any set of technology they are interested in--but there is no location based grouping.  &lt;/p&gt;

&lt;p&gt;This led to many asking the same question: are there any other AWS Community Builders near me?&lt;/p&gt;

&lt;p&gt;The manager of the program, &lt;a href="https://twitter.com/jasondunn"&gt;Jason Dunn&lt;/a&gt;, agreed to share some anonymized location data with me and I volunteered to draft a quick proof-of-concept map that would display the locations.  &lt;/p&gt;

&lt;p&gt;I initially implemented this via &lt;a href="https://developers.google.com/maps/documentation/javascript/overview"&gt;Google Maps JS API&lt;/a&gt; (&lt;a href="https://github.com/jdonboch/acbmap"&gt;code can be found here&lt;/a&gt;) but with the &lt;a href="https://aws.amazon.com/blogs/aws/amazon-location-service-is-now-generally-available-with-new-routing-and-satellite-imagery-capabilities/"&gt;release of AWS Location Service&lt;/a&gt;, I decided it was worth trying to experiment with this new service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Amazon Location Service?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/location/"&gt;Amazon Location Service&lt;/a&gt; is a new set of resources to support maps and location-based use cases such as geocoding, routing, asset/device tracking and geo-fence events.&lt;/p&gt;

&lt;p&gt;As of the writing of this, the AWS Location Service has been GA for less than 2 weeks.  I look forward to new features and integrations that will inevitably come in the future as the service continues to get some exercise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Impressions (of Maps and Geocoding)
&lt;/h3&gt;

&lt;p&gt;Here are my initial impressions.  I have only really experimented with maps and geocoding to support this simple use case.  &lt;/p&gt;

&lt;p&gt;The routing, tracking and geofencing components look very interesting but I don't have any direct experience with them yet.&lt;/p&gt;

&lt;h4&gt;
  
  
  Good: Great integration with other AWS Services
&lt;/h4&gt;

&lt;p&gt;If you are already in the AWS eco-system, using Amazon Location Services will be very easy as you can continue to use the tools and SDKs you are already using.  You can control access and security via IAM, automate deployments via CloudFormation, monitor with CloudWatch, be notified of location-based events via Amazon EventBridge, etc.&lt;/p&gt;

&lt;h4&gt;
  
  
  Good: Creates an abstraction for various location providers.
&lt;/h4&gt;

&lt;p&gt;Amazon currently supports its clients using &lt;a href="https://www.esri.com/en-us/home"&gt;Esri&lt;/a&gt; or &lt;a href="https://www.here.com/"&gt;HERE&lt;/a&gt; data sets and services.  When you create Amazon resources for maps, places, etc you choose which provider you want to supply the data.  Each provider has their own strengths and weakness but you can now switch easily without needing to integrate with each unique location provider API.  As new providers are added, you can continue to leverage the same interface.&lt;/p&gt;

&lt;h4&gt;
  
  
  Good: Better privacy and security
&lt;/h4&gt;

&lt;p&gt;AWS does not have rights to sell your data or use it for advertising purposes (can Google say that?).  They also anonymize all your requests before submitting them to vendors.  Any tracking and geofence data is stored only in your AWS account (not 3rd parties) which gives you the power to control and secure that information.&lt;/p&gt;

&lt;h4&gt;
  
  
  Not so good: Free Tier/Pricing
&lt;/h4&gt;

&lt;p&gt;AWS offers a free tier but it is only a 3-month trial.  After 3 months, you are on the hook for your requests based usage.  &lt;a href="https://cloud.google.com/maps-platform/pricing"&gt;Google Maps free-tier equivalent&lt;/a&gt; (free mobile usage and $200 maps credit each month) does not expire and is much more generous.  The maps and geocoding prices are competitive with other services but Amazon charges on a per-tile basis when many other providers charge on a per map-load basis.  This means if your users are zooming and panning frequently, you will likely be paying more for Amazon Location Services.&lt;/p&gt;

&lt;p&gt;This pricing makes Amazon Location Services a little less desirable for individuals, small prototyping efforts, and mobile map applications that are extremely price sensitive as you'll probably end up paying when other providers (i.e. Google) you can do a lot more with their free tier.&lt;/p&gt;

&lt;p&gt;I hope that Amazon creates a more permanent free-tier for the mapping service at least.&lt;/p&gt;

&lt;p&gt;&lt;del&gt;#### Not so good: Lacking access controls for anonymous access to public maps.&lt;br&gt;
Amazon Location Services provides an API that can be connected to other map libraries (i.e. &lt;a href="https://github.com/maplibre/maplibre-gl-js"&gt;MapLibre&lt;/a&gt; and &lt;a href="https://github.com/tangrams/tangram"&gt;Tangram&lt;/a&gt;).  This is sort of nice but it requires you to use a Cognito Identity Pool with anonymous access enabled if you want to display on a public website.  This is well documented in their &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/amazon-location-developer-guide.pdf.pdf"&gt;developer guide&lt;/a&gt; but I feel like there are some access-controls lacking as there is no way to lock down your anonymous credentials to a particular IP or HTTP referrer like you can do with Google's API Keys.  This means someone could grab the Cognito Identity Pool ID from the Javascript source code in the browser and use it for their own map requests.  This seems like a big flaw and hoping some additional Conditional support could be added to IAM policies to better control anonymous access.&lt;/del&gt; Someone pointed me in the right direction here and this is possible: &lt;a href="https://dev.to/jdonboch/restricting-unauthenticated-aws-access-by-referer-and-ip-similar-to-google-api-keys-1118"&gt;check out my article here on how to lock down AWS Location Service by referer or IP, similar to Google Maps API keys.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the map and geocoding the data
&lt;/h2&gt;

&lt;p&gt;To complete this proof of concept, I needed the following:&lt;/p&gt;

&lt;p&gt;Given a list of locations, I needed to 1) geocode the locations to get their latitudes and longitudes and then 2) create a static map webpage to display the locations as markers on a map.  Bonus points if we can add some metadata on the markers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The map
&lt;/h3&gt;

&lt;p&gt;There was not much innovation for the map part of the project and just a quick proof of concept was the goal.  It was accomplished by walking through the &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/amazon-location-developer-guide.pdf.pdf"&gt;following sections of the AWS Location Service Developer Guide&lt;/a&gt; which is a great resource to get started! :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page 27: &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/authenticating-using-cognito.html"&gt;Allowing unauthenticated guest access to your
application using Amazon Cognito&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Page 33: &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/maps-prerequisites.html#create-map-resource"&gt;Creating a Map Resource&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Page 34: &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/maps-prerequisites.html#tutorial-mapbox-identity-pool"&gt;Authenticating your requests&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Page 35: &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/tutorial-maplibre-gl-js.html"&gt;Using MapLibre GL JS with Amazon Location Service&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also used some MapLibre tutorials below to help create the markers, icons and pop ups on the map:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://maplibre.org/maplibre-gl-js-docs/example/add-image/"&gt;Add icon to map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maplibre.org/maplibre-gl-js-docs/example/cluster/"&gt;Create and style clusters (stole the pop up code here)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The geocoding process
&lt;/h3&gt;

&lt;p&gt;We need to &lt;a href="https://docs.aws.amazon.com/location/latest/developerguide/places-prerequisites.html#create-place-index-resource"&gt;create a Place Index resource&lt;/a&gt; to support the geocoding.&lt;/p&gt;

&lt;p&gt;To do the geocoding and create the GeoJSON that is displayed by the map, I wrote a quick Python script to perform that. This was fairly easy since the geocoding actions have been added to boto3 (i.e. the Python AWS SDK).  &lt;/p&gt;

&lt;p&gt;Unfortunately, the only location information that we had for Amazon Community Builders was Country.  This resulted in many  duplicate markers being generated which the mapping library was not happy about.  In the end, I adjusted the logic of the geocoding script to create a single point for each country but then add properties to the point that indicated the number of people in that country.  This member count property could then be displayed as a pop up on the map when the user hovered over the map marker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Results
&lt;/h2&gt;

&lt;p&gt;To see the final code and the geocoding script, &lt;a href="https://github.com/jdonboch/acbmap-aws"&gt;check out the acbmap-aws repo on GitHub&lt;/a&gt;.  I tried to add more details to that README with the specifics if you want to try and run this locally.&lt;/p&gt;

&lt;p&gt;You can find the a hosted version of the AWS Community Builders map, powered by Amazon Location Services, at the following location:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://acbmap.humbleg.com/aws/"&gt;https://acbmap.humbleg.com/aws/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>maps</category>
      <category>geo</category>
      <category>community</category>
    </item>
    <item>
      <title>Using Athena data connectors to  visualize DynamoDB data with AWS QuickSight</title>
      <dc:creator>Jared Donboch</dc:creator>
      <pubDate>Sun, 21 Mar 2021 13:20:53 +0000</pubDate>
      <link>https://forem.com/aws-builders/finally-dynamodb-support-in-aws-quicksight-sort-of-2lbl</link>
      <guid>https://forem.com/aws-builders/finally-dynamodb-support-in-aws-quicksight-sort-of-2lbl</guid>
      <description>&lt;h2&gt;
  
  
  Some context and history
&lt;/h2&gt;

&lt;p&gt;Almost 2 years ago, I started experimenting with QuickSight to solve some of the BI issues of the company I was working for. I appreciated QuickSight's first-class integration with many AWS data services and low cost in comparison to other similar tools.  It afforded us the ability to rapidly prototype analyses and dashboards.  One glaring missing feature that left us scratching our heads was the lack of DynamoDB has a data source option. &lt;/p&gt;

&lt;p&gt;I asked &lt;a href="https://stackoverflow.com/questions/57775511/visualize-dynamodb-data-in-aws-quicksight" rel="noopener noreferrer"&gt;the StackOverflow hive-mind how to Visualize DynamoDB in AWS Quicksight on StackOverflow&lt;/a&gt; and it has become the most upvoted QuickSight question on the platform because there is a demand for this feature and there was no direct answer... until recently.&lt;/p&gt;

&lt;p&gt;Most of the work-around solutions proposed involved exporting (i.e. duplicating) your DynamoDB data to a different repository such as S3 or RDS that could then be added as a data source in QuickSight.  We ended up creating scheduled Glue jobs that would move DynamoDB data to S3.  The S3 data was then crawled via AWS Glue Crawlers and exposed as AWS Athena tables which were then added as Quick Sight data sets.  This worked but was more custom infrastructure than was desirable and also didn't allow for real time direct queries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/blogs/big-data/query-any-data-source-with-amazon-athenas-new-federated-query/" rel="noopener noreferrer"&gt;In late 2019, AWS announced you could query any datasource with Amazon Athena's new federated query feature.&lt;/a&gt;.  This was cool and they even showed in diagrams the concept of querying DynamoDB but this required us to develop and maintain our own implementation of this connector and also this feature was only in Preview and AWS QuickSight integration was not updated to allow the usage of this new Athena feature.&lt;/p&gt;

&lt;p&gt;In March 2020, I saw &lt;a href="https://docs.aws.amazon.com/athena/latest/ug/athena-prebuilt-data-connectors-dynamodb.html" rel="noopener noreferrer"&gt;that AWS Athena announced a prebuilt Data Connector for DynamoDB&lt;/a&gt;.  This was exciting as I was able to quickly setup a data connector in Athena that could actually view and query DynamoDB data without any custom code  but this feature was still in Preview and there was no support in QuickSight yet.&lt;/p&gt;

&lt;p&gt;Fast forward, it's a year later and &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/11/amazon-athena-announces-availability-of-engine-version-2/" rel="noopener noreferrer"&gt;Athena Data Connectors have been become GA with the Athena Engine Version 2 in a handful of regions&lt;/a&gt; and the integration features in QuickSight that allow you to select the required Athena work groups and data sources are present.  &lt;/p&gt;

&lt;p&gt;This should be enough to make things work until DynamoDB becomes a first-class data source in Amazon QuickSight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Detailed Process
&lt;/h2&gt;

&lt;p&gt;Using the Athena Data Connectors as part of Athena Engine Version 2, I was able to finally visualize DynamoDB data in QuickSight without creating any bespoke resources or duplicating the data to another data source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites/Assumptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You have data in DynamoDB tables that you want to visualize in QuickSight&lt;/li&gt;
&lt;li&gt;You have access to a user/role in your account with IAM access&lt;/li&gt;
&lt;li&gt;You have a bucket created that can be used to store data Athena query results and data connector spill data.

&lt;ul&gt;
&lt;li&gt;If this going to be used in production with large queries, I'd recommend adding &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html" rel="noopener noreferrer"&gt;S3 lifecycle policies&lt;/a&gt; to this bucket to ensure this bucket doesn't grow uncontrollably.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;You are using a region that supports both

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/athena/latest/ug/engine-versions-reference.html#engine-versions-reference-0002-limits" rel="noopener noreferrer"&gt;Athena Engine Version 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/general/latest/gr/quicksight.html" rel="noopener noreferrer"&gt;QuickSight&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Switch to using Athena Engine Version 2
&lt;/h3&gt;

&lt;p&gt;If you don't already have an Athena workgroup that uses Athena Engine Version 2, this needs to be created.&lt;/p&gt;

&lt;p&gt;At the time of this writing, the default primary workgroup uses Athena Engine Version 1 but they also indicate that these workgroups will be automatically upgraded at some point in the future.  &lt;/p&gt;

&lt;p&gt;Ain't nobody got time for AWS to upgrade so we need to go ahead (create, if needed) and switch to a workgroup that uses Athena Engine Version 2.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Athena console, select the workgroups tab.
&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%2Fzgf1d5n0sbub4oo6ha7h.png" alt="Athena Workgroup Tab"&gt;
&lt;/li&gt;
&lt;li&gt;Ensure that a workgroup exists that is "Athena Engine version 2" and switch to it.
&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%2F6bfv10nmzbksry2rga3t.png" alt="Athena Engine V2 workgroup"&gt;

&lt;ol&gt;
&lt;li&gt;Most likely, you will not have one and you will need to create one by clicking the "Create Workgroup" button.  You can pretty much use the defaults for everything such ensure you select "Manually choose an engine version now." and ensure "Athena engine version 2 (recommended)" is selected when creating the workgroup.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create the DynamoDB Athena Data Connector
&lt;/h3&gt;

&lt;p&gt;Once we an Athena Engine version 2 workgroup created, let's go ahead and create our DynamoDB Athena Data Connector.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the Data Sources tab of the Athena console and choose "Connect data source" button.
&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%2Fnt6vzc6ix6vjq2mcb81p.png" alt="Athena data sources"&gt;
&lt;/li&gt;
&lt;li&gt;In first step of the Data Sources wizard, select the "Query a data source" option, then select "Amazon DynamoDB", then click the next button.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The second and final step, AWS wants you to specify the connector lambda.  This does not exist yet so we need to deploy it by clicking the "Configure new AWS Lambda function" button.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This button opens a new window to the Lambda console to deploy the prebuilt AthenaDynamoDBConnector application.&lt;/li&gt;
&lt;li&gt;Under Application settings, at a minimum to need to provide values for &lt;code&gt;SpillBucket&lt;/code&gt; and &lt;code&gt;AthenaCatalogName&lt;/code&gt; parameters as they do not have any defaults.
&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%2F3kfbxm702c17yvcgvssc.png" alt="AthenaDynamoDBConnector deployment"&gt;
&lt;/li&gt;
&lt;li&gt;Click Deploy&lt;/li&gt;
&lt;li&gt;After the the Lambda is deployed, head back to your Athena console window.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Now that our AthenaDynamoDBConnector function is deployed, click the refresh icon next to the "Choose Lambda function" dropdown list and you should now see the newly deployment lambda function.  Select your function, give catalog a name, and click the "Connect" button.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;At this point, you should be able to use Athena to query your DynamoDB data using the data source and catalog name you gave your resources.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpum59hktknux138qzamj.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%2Fpum59hktknux138qzamj.png" alt="Athena query"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Granting QuickSight IAM role Lambda permissions
&lt;/h3&gt;

&lt;p&gt;The Athena Data Connector works by invoking a Lambda to query and return DynamoDB data.  Therefore we need to give QuickSight's service role permissions to invoke the Lambda function.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the IAM Console and select "Roles" &lt;/li&gt;
&lt;li&gt;Find and click the QuickSight service role.  The role name should begin with something like "aws-quicksight-service-role", i.e. "aws-quicksight-service-role-v0".&lt;/li&gt;
&lt;li&gt;Click "Attach Policies" &lt;/li&gt;
&lt;li&gt;Select the "AWSLambdaRole" and click "Attach Policy"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configuring QuickSight to use the new connector
&lt;/h3&gt;

&lt;p&gt;Now, let's get this working with QuickSight!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open QuickSight and navigate to the Data Sets menu.&lt;/li&gt;
&lt;li&gt;Click "New Dataset" and select Athena as data source.
&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%2F6w75tfj0vpdado60jkr2.png" alt="Select Athena Data source"&gt;
&lt;/li&gt;
&lt;li&gt;Select the workgroup you created previously that uses the Athena Engine version 2.
&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%2Fxohbonh5muuixj1n1bga.png" alt="Choose athena workgroup"&gt;
&lt;/li&gt;
&lt;li&gt;Now select the catalog that we created for the DynamoDB connector in the previous steps.
&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%2Fxzl00nnzap174l1sg4si.png" alt="DynamoDB tables in quicksight"&gt;

&lt;ol&gt;
&lt;li&gt;If you get a "You don’t have sufficient permissions to connect to this dataset or run this query." error, this is most likely because you did not properly add Lambda Invoke permissions to the QuickSight role.  See the step above to ensure this is setup.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Now choose if you want to cache the data via SPICE or directly query the data.  Directly querying will give you the ability to see real-time data in DynamoDB but the performance and cost may suffer since it is not cached.

&lt;ol&gt;
&lt;li&gt;You can add additional DynamoDB table data sets and continue to reuse the Athena Engine V2 data source you created in Step 2 above.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create your visualizations!
&lt;/h3&gt;

&lt;p&gt;Now that we have a DynamoDB data set (via Athena and the DynamoDB data connectors) created, we can finally visual DynamoDB data via analyses and dashboards.&lt;br&gt;
   &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu7m11gnko8l7gqiw16y.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%2Fuu7m11gnko8l7gqiw16y.png" alt="DyanmoDB data in Quicksight"&gt;&lt;/a&gt;&lt;br&gt;
I am just using a tiny sample DynamoDB dataset for this example so it's not the most interesting visualization but hopefully you get the idea!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;It's been a long time coming but glad it's finally possible to get DynamoDB data into QuickSight without custom resources and duplicate data.  This is just one example of the many data sources that can now be more easily be added via the Athena Engine Version 2 data connectors and the new ability to choose Athena workgroups and Athena catalogs in QuickSight.&lt;/p&gt;

&lt;p&gt;I'm sure this article will eventually be obsolete sooner rather than later as technology changes and Amazon continues to release new features but it was a good exercise to explore some of these new features.&lt;/p&gt;

&lt;p&gt;Please feel free to drop me a line if this was helpful or if I you have any suggestions on thing that could be improved or added.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>quicksight</category>
      <category>athena</category>
      <category>bi</category>
    </item>
  </channel>
</rss>
