<?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: Marc-Henry Geay</title>
    <description>The latest articles on DEV Community by Marc-Henry Geay (@mhgeay).</description>
    <link>https://dev.to/mhgeay</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%2F265247%2F8f85e252-723d-45a2-920e-d175ec36a4b8.png</url>
      <title>DEV Community: Marc-Henry Geay</title>
      <link>https://dev.to/mhgeay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mhgeay"/>
    <language>en</language>
    <item>
      <title>CyberGordon - CDN cache optimization</title>
      <dc:creator>Marc-Henry Geay</dc:creator>
      <pubDate>Sun, 30 Oct 2022 12:39:52 +0000</pubDate>
      <link>https://dev.to/aws-builders/cybergordon-cdn-cache-optimization-2o7a</link>
      <guid>https://dev.to/aws-builders/cybergordon-cdn-cache-optimization-2o7a</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MnAS1phC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fslzet84kdx1e5ndd7y2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MnAS1phC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fslzet84kdx1e5ndd7y2.png" alt="CyberGordon CDN cache" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web cache introduction
&lt;/h2&gt;

&lt;p&gt;A Web cache is a system for optimizing the Web page loads. It is implemented both client-side (browser) and server-side (Content Delivery Network (CDN)/Web server).&lt;/p&gt;

&lt;p&gt;Web cache optimization brings several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the user side, &lt;strong&gt;the web page is displayed faster&lt;/strong&gt;; very useful during mobile browsing. 🐎&lt;/li&gt;
&lt;li&gt;On the server side, the load is lower, and with the cloud model of pay-as-you-go, this results in &lt;strong&gt;cost reductions&lt;/strong&gt;. 💰&lt;/li&gt;
&lt;li&gt;And you &lt;strong&gt;reduce your carbon footprint&lt;/strong&gt; by eliminating many unnecessary requests processed by browsers and servers. 🌱&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The context of my project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cybergordon.com/"&gt;CyberGordon&lt;/a&gt; website static resources (images, css, js, json) are hosted on a AWS S3 Bucket behind AWS CloudFront (CDN). You can have a presentation of the architecture on my &lt;a href="https://dev.to/aws-builders/the-genesis-and-architecture-of-my-cybergordon-project-24pm"&gt;specific blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;HTTPS queries of these static resources represent half of the overall query volume&lt;/strong&gt;: an average user browsing (home page --&amp;gt; create an analysis --&amp;gt; view results) represents 9 static and 8 dynamic queries. The latter requests are used to create the analysis and retrieve the results as well as the overall CyberGordon statistics displayed on the homepage.&lt;/p&gt;

&lt;p&gt;So I started working in March 2021 based on a &lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/improve-your-website-performance-with-amazon-cloudfront/"&gt;AWS post&lt;/a&gt; to optimize the cache of these static resources as much as possible and I got good results and a lower bill! 👌&lt;/p&gt;

&lt;h2&gt;
  
  
  How I optimized the cache
&lt;/h2&gt;

&lt;p&gt;To achieve my goal, I used 3 features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cache-Control header: add a cache directive on static resources sent to users to be respected by the web browser.&lt;/li&gt;
&lt;li&gt;CloudFront cache policies: refine the CDN to be able to manage the default cache and reset it if needed.&lt;/li&gt;
&lt;li&gt;File versioning: long-term caching of resources that almost never change and replacing them with new ones quickly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Production &amp;amp; automation requirements&lt;/strong&gt;&lt;br&gt;
It is essential to have a strategy allowing to have the hand on the cache in case of change of object (security fix, dependency upgrade, new logo, etc) to reset it on all the chain so that the user get the new object when he returns on the Web site.&lt;br&gt;&lt;br&gt;
I have decided to have a maximum delay of 30 minutes in case an item needs to be changed.&lt;/p&gt;

&lt;p&gt;To avoid manual tasks, I also tried to automate all repetitive tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  User cache - Cache-Control header
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control"&gt;Cache-Control HTTP header&lt;/a&gt; is useful to give instructions on the cache of the object received by a Web browser or an intermediary service (proxy, CDN, ...). In a simplified way, this header received by the web browser indicates if the object can be cached and for how long.&lt;/p&gt;

&lt;p&gt;I want visitors to cache as much of the object as possible for one or two visits, so &lt;strong&gt;I set a 30 minute cache on the client side for web pages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I added Cache-Control header on the following S3 objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;html - &lt;code&gt;max-age=1800&lt;/code&gt; --&amp;gt; 30 min cache&lt;/li&gt;
&lt;li&gt;js, css, svg, jpg, png, gif, pdf, json (except for statistic data), txt, xml - &lt;code&gt;max-age=31536000, immutable&lt;/code&gt; --&amp;gt; 1 year cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To automate the addition of this header during deployment, &lt;strong&gt;I updated my CyberGordon Terraform configuration&lt;/strong&gt; to automatically add a specific Cache-Control header based on the file extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  CDN cache - CloudFront cache policies
&lt;/h3&gt;

&lt;p&gt;CloudFront is extremely powerful but when you want to push its settings further, it becomes quite complex. I won't do a detailed presentation, as that would be a whole post, but you can learn more about it in the &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehavior.html"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html"&gt;Cache Policy&lt;/a&gt; allows you to specify the criteria (URL path, query string, headers, etc) to store an object in the CDN cache. In addition to the cache, we can enable compression of resources in transit to reduce the volume transferred between the CDN and the Web browser.&lt;/p&gt;

&lt;p&gt;I have applied 3 Cache Policies on CloudFront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Default (*)&lt;/code&gt; --&amp;gt; All static resources: 30 days cache with Gzip + Brotli compressions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assets/json/stats_*&lt;/code&gt; --&amp;gt; Statistic updated every 30 min, so 30 min cache with Gzip + Brotli compressions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;request/*, get-request/*, r/*, contact-message&lt;/code&gt; --&amp;gt; Dynamic requests, so no cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These Cache Policies are easily managed trough Terraform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static resources cache - File versioning
&lt;/h3&gt;

&lt;p&gt;Cache file versioning aka &lt;strong&gt;Cache busting&lt;/strong&gt; is a very simple but very effective way to force a client to retrieve the new version of an updated resource by adding the 'version' of the resource to the file name.&lt;/p&gt;

&lt;p&gt;The example below shows a real case of the Bootstrap JavaScript file used on the CyberGordon website.  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A portion of the file hash (SHA-256) is added to the file name&lt;/strong&gt; in order to have a unique name for each Bootstrap version upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In the code of the HTML page, the unique name of the Bootstrap file is used&lt;/strong&gt;. The web page (index.html) keeps its file name for life and is only cached for 30 minutes. Therefore, if a static resource is updated, only its reference in the HTML page is changed with a new hash, and the client will have loaded the new version within 30 minutes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JnGzGJb---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47ji8fabh5p9fwy0d9mm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JnGzGJb---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/47ji8fabh5p9fwy0d9mm.png" alt="Object versioning principe" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This method works for every static file type: I use it for all JavaScript, CSS, images (like the logo shown as an example highlighted in gray in the image above) and also JSON file for changelog.&lt;/p&gt;

&lt;p&gt;Here again, &lt;strong&gt;I have automated with Terraform&lt;/strong&gt; the creation of the file name including the hash as well as the insertion of this unique name in the HTML pages that reference it. The code is horrible to read: a mixture of Join, Regex and Substr functions... but its works!&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens if I want to push a new resource?
&lt;/h2&gt;

&lt;p&gt;CloudFront is our intermediate cache that we can act on directly to flush the cache and push updated resources to the clients.&lt;/p&gt;

&lt;p&gt;This is what happens when I have to update a resource:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Normally a new client retrieves static resources (e.g. Bootstrap 4.6) from the CloudFront cache and then caches them on his web browser (or even on the enterprise proxy).&lt;/li&gt;
&lt;li&gt;When updating a resource (Bootstrap 4.7), I push the new file via Terraform which changes its filename with the hash and the HTML pages using it. Then the unitary CloudFront cache of these pushed files is cleared.&lt;/li&gt;
&lt;li&gt;Consequently, the next time the client visits (at least 30 minutes later), his browser will retrieve the HTML web page and thus the new referenced resource (Bootstrap 4.7).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I88kuA0y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw4mpytmgnn2yleet81t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I88kuA0y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lw4mpytmgnn2yleet81t.png" alt="New resource update process" width="750" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CyberGordon cache strategy
&lt;/h2&gt;

&lt;p&gt;With these 3 techniques and update processes in place, I have summarized here the principles and values of caching.&lt;/p&gt;

&lt;p&gt;The cache strategy &lt;strong&gt;follows these 3 principles&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client-side cache should not exceed 30 minutes for Web pages (HTML). After this time, the client must retrieve the latest version of the HTML files from the server.&lt;/li&gt;
&lt;li&gt;The client keeps all static resources (img, js, css, ...) unmodified and versioned in the cache for up to one year.&lt;/li&gt;
&lt;li&gt;If an resource is modified, it must be possible to reset the CDN cache so that the client can retrieve the latest version after its browser-side cache has expired (so in less than 30 min).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This table &lt;strong&gt;summarizes the current cache strategy&lt;/strong&gt; on CyberGordon:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Versioning&lt;/th&gt;
&lt;th&gt;CloudFront cache&lt;/th&gt;
&lt;th&gt;Browser cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTML + sitemap/robots&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON Changelog + Engine lists&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;1 month&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON Statistic (Updated)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion &amp;amp; results
&lt;/h2&gt;

&lt;p&gt;The implementation of these techniques was not an easy task and many tests were carried out, but the result is relevant since it went into production in June 2021:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;90% fewer HTTPS requests and 99% less volume (GB) transferred after the first visit.&lt;/li&gt;
&lt;li&gt;The cache hit rate of static resources jumped from 0 to 86% and the volume in GB transferred to the server (origin) decreased by almost 40%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pictures below show the result without cache and then with cache.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tQYfP5TQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct1vw15v7xk03ssbtclx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tQYfP5TQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ct1vw15v7xk03ssbtclx.png" alt="CyberGordon homepage without cache (ignored)" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wdMVj3Ld--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb1frqv82o1gvnkw8van.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdMVj3Ld--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb1frqv82o1gvnkw8van.png" alt="CyberGordon homepage with cache" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, in addition to the implementation of the cache, the &lt;strong&gt;compression of static resources during the network transfer and the file source reduces the amount of data transferred&lt;/strong&gt;. Images can be compressed easily &lt;a href="https://compresspng.com/"&gt;with online tools&lt;/a&gt; without loss of visual quality.&lt;br&gt;
Compressing the resources allowed me to reduce the overall size of the resources by 13%.&lt;/p&gt;

&lt;p&gt;Finally I was able to automate the whole implementation with the powerful functions of Terraform. Without Terraform, the work at each resource modification will be very hard...&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloudfront</category>
      <category>finops</category>
    </item>
    <item>
      <title>The genesis and architecture of my CyberGordon project</title>
      <dc:creator>Marc-Henry Geay</dc:creator>
      <pubDate>Sun, 30 Oct 2022 10:56:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/the-genesis-and-architecture-of-my-cybergordon-project-24pm</link>
      <guid>https://dev.to/aws-builders/the-genesis-and-architecture-of-my-cybergordon-project-24pm</guid>
      <description>&lt;h2&gt;
  
  
  Quick introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5QzlhE9e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbnlcpmsoh8jct8e4azm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5QzlhE9e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbnlcpmsoh8jct8e4azm.png" alt="CyberGordon operating principle" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://cybergordon.com"&gt;CyberGordon&lt;/a&gt; quickly provides you threat and risk information about observables such as IP addresses or domain names by querying multiple threat intelligence sources.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks to each source that provides free access to great Threat Intelligence against phishing and malware. Without them, CyberGordon would have not been there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CyberGordon ?
&lt;/h2&gt;

&lt;p&gt;Whether it be during my investigations at work or personal surfing sessions, I’m too lazy to &lt;strong&gt;use several sources&lt;/strong&gt; to check if a domain or email address is suspicious or malicious. Some &lt;a href="https://osintframework.com/"&gt;awesome OSINT tools&lt;/a&gt; exist, but I didn’t have one to aggregates them all into one simple web interface. On top of that, I wanted to start by building a usable and useful tool on &lt;a href="https://aws.amazon.com/"&gt;AWS infrastructure&lt;/a&gt; that I could share with my entourage. I would have liked to share CyberGordon widely, but I’m constrained by the query limits that free API sources provide. Lastly, the lock down during the COVID-19 crisis gave me a lot of time, a rare resource that considerably contributed in completing CyberGordon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Well why “&lt;em&gt;Gordon&lt;/em&gt;” ?&lt;/strong&gt;&lt;br&gt;
As a Batman fan, I chose the &lt;a href="https://en.wikipedia.org/wiki/James_Gordon_(character)"&gt;commissioner James Gordon&lt;/a&gt;, a friend and reliable informant of the Dark Knight 🦇 🕵️&lt;/p&gt;

&lt;p&gt;On April 2021, Gordon became CyberGordon to better reflect its function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kSk1d4RN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc9cyk3coh9wqwvu7yl3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kSk1d4RN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc9cyk3coh9wqwvu7yl3.png" alt="CyberGordon homepage capture" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;p&gt;When I built CyberGordon, I tried to follow several rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple: get results after pasting observable(s)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither I or my entourage would use a tool that has an extremely complex GUI or that requires sophisticated information for submission. The aim is to copy/paste one or more observables even if they are in a messy format (listed, quoted or in CSV format) submit on a unique form and get a readable summary. I’m still working on improving this last point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scalable: add easily new sources&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scalability often referred to the ability to manage automatically the capacity depending on the user’s demand. My intention was humble: I wanted an evolving system where I can add, or update, easily a source (called &lt;em&gt;engine&lt;/em&gt;) without impacting the existing ones and without adding delay during processing of the user’s observable submission.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Almost free: use adapted and cost-effective services&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my first tool on AWS and as a non-profit service, the cost was of course the most important criteria. All major cloud service providers offer free tiers use for few of their main services for a duration of one year (sometimes less) or for life. I spent some time looking for simple and almost free AWS services before building a draft.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Serverless: minimal maintenance&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It started as a challenge: being a relatively simple tool, I tried to avoid managing a Linux server, even though if I loved managing Debian servers previously… I wanted to test &lt;em&gt;functions&lt;/em&gt; (containers of code), where you only manage the code, while the underlying layers (runtime, OS, hardware) are managed by AWS.&lt;br&gt;
Of course, code maintenance has to be done at least for each runtime update (Python 3.8 to Python 3.9 for example).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure: apply best practices&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last but not least, even tough the manipulated data is not confidential, I have applied some principles: all data — in motion and at rest — is encrypted with AWS managed key (free), permission’s resources are restricted to the minimal needs (least privileges), public exposure is limited and management actions (API) and users’ HTTP request logs are stored for a duration of 6 months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt with Slack
&lt;/h2&gt;

&lt;p&gt;Before hosting CyberGordon entirely on AWS, I tried to build a front-end on Slack as a ‘bot’ using &lt;em&gt;Slack Commands&lt;/em&gt; feature and processing them on AWS. It works like a charm with one engine, but with two or more it is a mess and unusable. Slack is not suitable for presenting multiple results ; it is a good chat tool for “one question, one short response” capability, but not as a reporting tool...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QITzn7_A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4excr7i3wypxbivw5vbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QITzn7_A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4excr7i3wypxbivw5vbf.png" alt="Request with Slack Command" width="700" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slack request is sent to an HTTPS endpoint (hosted on &lt;em&gt;AWS API Gateway&lt;/em&gt;) and forwarded to the back-end. Results are then returned using the &lt;em&gt;URL incoming webhook&lt;/em&gt; included in the Slack request. As you can see below, results are quickly unreadable when using 2 engines…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n3Tu2YJC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7vrxuvek5s7f9rbwc52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n3Tu2YJC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7vrxuvek5s7f9rbwc52.png" alt="Results with Slack’s Incoming Webhook" width="700" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A teammate suggested I generate reports on a webpage and send links to Slack user. However, after a lot thinking, this hybrid solution didn’t suit me because it limits the user’s scope only to my Slack workspace users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current architecture and how it works
&lt;/h2&gt;

&lt;p&gt;To get all AWS capabilities and cheapest prices, all resources are hosted in “&lt;em&gt;US East (Northern Virginia)” &lt;a href="https://aws.amazon.com/about-aws/global-infrastructure/regions_az/"&gt;region&lt;/a&gt;&lt;/em&gt;, except for 2 resources invoked near the user location.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short summary&lt;/strong&gt;&lt;br&gt;
On website (1) you paste an observable and submits it ; request is parsed (2), sent to a queue (3), dispatched to engines (4) that queries API sources. During this background work, you’re forwarded to the results page (5).&lt;/p&gt;

&lt;p&gt;Components are represented on this diagram and described in detail later on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Krbl6rEg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7201a8bonxnu8tbqgph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Krbl6rEg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7201a8bonxnu8tbqgph.png" alt="CyberGordon Architecture (high level)" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Static website
&lt;/h3&gt;

&lt;p&gt;All front-web assets are stored on a single S3 bucket (object storage). To provide cache, reduced latency and encrypted traffic (HTTPS TLS 1.3), a CloudFront distribution (CDN) is used ; the S3 Bucket policy only allows traffic from CloudFront.&lt;/p&gt;

&lt;p&gt;The domain cybergordon.com points to a CloudFront distribution and the DNS zone “cybergordon.com” is entirely managed by AWS Route 53 (DNS service).&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Request pipeline
&lt;/h3&gt;

&lt;p&gt;By clicking on “&lt;em&gt;Analyze!”&lt;/em&gt;, an HTTP POST request with observable(s), passes through the &lt;strong&gt;&lt;em&gt;CyberGordon-Request&lt;/em&gt;&lt;/strong&gt; Lambda@Edge function: a Python code deployed on multiple geographical points to be executed closer to the user.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;CyberGordon-Request&lt;/em&gt; function generates a request ID (UUID version 4) and parses the observable(s) into 7 predefined types list: IPv4, FQDN, URL, MD5, SHA-1, SHA-256 and Email address. Basically, the function compares the request body with 7 flexible regex that accept new line (\n) or space between each observable.&lt;/p&gt;

&lt;p&gt;Then the user is forwarded (HTTP 302 Found) to the results page that is described below.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Queue pipeline
&lt;/h3&gt;

&lt;p&gt;Small but powerful part to dispatch observables to engines depending of the types they can check against sources. The &lt;strong&gt;&lt;em&gt;CyberGordon-Queue&lt;/em&gt;&lt;/strong&gt; SNS topic receives message and send immediately a copy to each subscriber that accepts submitted observables type(s).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Engine pipeline
&lt;/h3&gt;

&lt;p&gt;Each &lt;strong&gt;&lt;em&gt;CyberGordon-Engine&lt;/em&gt;&lt;/strong&gt; Lambda functions receives simultaneously the observable(s) list. The engine controls the integrity of the request (using the SHA-1 function), then gets, if applicable, the API token of the remote source in encrypted variables and queries it then in HTTPS. Finally results are stored in a DynamoDB Table (no-SQL database). Results from all engines are stored in a unique database record. In previous implementation, individual results were stored on S3 objects, with a fourfold increase in lead times to retrieve them!&lt;/p&gt;

&lt;p&gt;All engines query remote sources to get live and fresh information, except for the &lt;em&gt;Offline Feeds engine&lt;/em&gt; (E23): an hourly CloudWatch Event Rule (scheduled task) invokes a Lambda function (Python code) that downloads, transforms in a JSON format and overwrites the existing feed content stored in the main S3 bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Result pipeline
&lt;/h3&gt;

&lt;p&gt;Returning to the Request stage explained earlier (2. Request pipeline), the user is forwarded to the result page. When loaded, a JavaScript gets the request ID in URL Query Parameter (see example below) and then makes a call to the result endpoint &lt;code&gt;HTTP GET /get-result&lt;/code&gt;.&lt;br&gt;
This HTTP call is caught by the &lt;strong&gt;&lt;em&gt;CyberGordon-Results&lt;/em&gt;&lt;/strong&gt; Lambda@Edge function (like the Request function). This function reads the database record that contains all results and return it as a JSON Document.&lt;br&gt;
This JavaScript script is “&lt;em&gt;&lt;a href="https://datatables.net/"&gt;Datatables&lt;/a&gt;&lt;/em&gt;”, a great free JQuery plugin that generates super-easy HTML table from a JSON input. &lt;em&gt;Datatables&lt;/em&gt; provides exporting capabilities: all results are exportable in Excel, CSV, PDF files or in your clipboard for further analysis or archiving purposes if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result URL example:&lt;/strong&gt; &lt;a href="https://cybergordon.com/r/e3a3a0c9-33c0-46e1-a612-91788ee76d14"&gt;https://cybergordon.com/r/e3a3a0c9-33c0-46e1-a612-91788ee76d14&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Continuous improvements
&lt;/h2&gt;

&lt;p&gt;The current architecture is far from being perfect and suffers from several issues that are more or less obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Slowness when getting and merging results from each object result. I could merge engines to one function that could generates only one result object ; in this case the CyberGordon-Result function can be spiked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-enforce the security (input control)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The quality of the Python code, long way…&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide a front-end API and User Account system&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since 2020 I did some improvements which will be the subject of a future article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Backup config and code on S3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Industrialize deployment with CI/CD pipeline and Infrastructure as Code with Terraform.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m open to any remarks that will help improve &lt;a href="https://cybergordon.com"&gt;CyberGordon&lt;/a&gt; !&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Thanks to &lt;a href="https://www.linkedin.com/in/carole-boijaud-58271293"&gt;Carole Boijaud&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/youssef-sayegh-47916b2b"&gt;Youssef Sayegh&lt;/a&gt; and my darling for their careful proofreading.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LVCuv36T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqcxiq6b1lb78lj67j8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LVCuv36T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqcxiq6b1lb78lj67j8c.png" alt="CyberGordon logo" width="343" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn how I tuned CyberGordon Web cache with CloudFront on &lt;a href="https://dev.to/aws-builders/cybergordon-cdn-cache-optimization-2o7a"&gt;this blog post&lt;/a&gt;. More posts on my &lt;a href="https://mhgeay.fr/"&gt;personal blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
