<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title></title>
    <description>Dev + Design Blog from Haufe Group</description>
    <link>http://work.haufegroup.io</link>
    <atom:link href="http://work.haufegroup.io/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>How to Create a New Entra ID Enterprise Application and Configure Custom Attributes for SAML Login for AWS Cognito</title>
        <description>&lt;h1 id=&quot;how-to-create-a-new-entra-id-enterprise-application-and-configure-custom-attributes-for-saml-login-for-aws-cognito&quot;&gt;How to Create a New Entra ID Enterprise Application and Configure Custom Attributes for SAML Login for AWS Cognito&lt;/h1&gt;

&lt;p&gt;When integrating Entra ID (formerly Azure AD) with AWS Cognito for SAML login, it’s important to use a unique attribute to identify users. In this guide, we’ll walk you through the steps to create a new Enterprise Application in Entra ID and configure a custom attribute named &lt;code&gt;user.objectid&lt;/code&gt;. This attribute ensures that user identities remain consistent even if other attributes, such as last names, change.&lt;/p&gt;

&lt;h2 id=&quot;why-use-userobjectid&quot;&gt;Why use &lt;code&gt;user.objectid&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;user.objectid&lt;/code&gt; attribute is unique to each user in Entra ID and does not change, even if other user attributes are updated. This is particularly important for scenarios where an employee changes their last name, as other attributes will be updated with the new value. Using &lt;code&gt;user.objectid&lt;/code&gt; prevents the creation of a new local user in your application and ensures that existing user data is preserved.&lt;/p&gt;

&lt;h2 id=&quot;steps-to-create-a-new-enterprise-application-in-entra-id&quot;&gt;Steps to Create a New Enterprise Application in Entra ID&lt;/h2&gt;

&lt;h3 id=&quot;create-a-new-application&quot;&gt;Create a New Application&lt;/h3&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Navigate to the Azure Portal&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Open the Azure Portal and go to the Entra ID service.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Create a New Application&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;strong&gt;Enterprise applications&lt;/strong&gt; and select &lt;strong&gt;New application&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Choose &lt;strong&gt;Create your own application&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Select the option to &lt;strong&gt;Integrate any other application you don’t find in the gallery (Non-gallery)&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Type the name of your new application and create it.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/create_ent_app.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/create_ent_app.png&quot; alt=&quot;Create Enterprise Application&quot; style=&quot;width: 80%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;configure-single-sign-on-sso-login&quot;&gt;Configure Single Sign-On (SSO) login&lt;/h3&gt;
&lt;p&gt;Create the connection between Entra ID and your application by setting the login URL and the identity of your application.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Select Single Sign-On Method&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;In your Enterprise application, select the &lt;strong&gt;Single Sign-On&lt;/strong&gt; option from the left menu.&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;SAML&lt;/strong&gt; as the single sign-on method.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Edit Basic SAML Configuration&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Edit the &lt;strong&gt;Basic SAML Configuration&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Add the &lt;strong&gt;Identifier (Entity ID)&lt;/strong&gt; and &lt;strong&gt;Reply URL (Assertion Consumer Service URL)&lt;/strong&gt;.
        &lt;ul&gt;
          &lt;li&gt;The &lt;strong&gt;Identifier (Entity ID)&lt;/strong&gt; should follow the format: urn:amazon:cognito:sp:&lt;code&gt;cognito_userpool_id&lt;/code&gt;.&lt;/li&gt;
          &lt;li&gt;The &lt;strong&gt;Reply URL (Assertion Consumer Service URL)&lt;/strong&gt; should follow the format: https://&lt;code&gt;cognito_domain_url&lt;/code&gt;/saml2/idpresponse.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Save the changes&lt;/strong&gt; to the Basic SAML Configuration.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Save the changes&lt;/strong&gt; to the Basic SAML Configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/saml_sso_config.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/saml_sso_config.png&quot; alt=&quot;SAML SSO Configuration&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;configure-the-user-access-for-sso-login&quot;&gt;Configure the User Access for SSO login&lt;/h3&gt;
&lt;p&gt;Assign the users and groups that should have permissions to log in to your application.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Assign Users and Groups&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Select the &lt;strong&gt;Users and groups&lt;/strong&gt; option from the Enterprise application options.&lt;/li&gt;
      &lt;li&gt;Click on &lt;strong&gt;Add user/group&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Select the users and/or groups who should have access to your application.&lt;/li&gt;
      &lt;li&gt;Confirm your selections and save.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/sso_add_users.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/sso_add_users.png&quot; alt=&quot;Add Users for SSO&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;configure-user-attributes--claims-for-sso-login&quot;&gt;Configure User Attributes &amp;amp; Claims for SSO Login&lt;/h3&gt;

&lt;p&gt;Configure which Entra ID attributes should be used to log in to your application.&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;Edit User Attributes &amp;amp; Claims&lt;/strong&gt;
        &lt;ul&gt;
            &lt;li&gt;From the &lt;strong&gt;Single Sign-On&lt;/strong&gt; option for your Enterprise application, edit the &lt;strong&gt;User Attributes &amp;amp; Claims&lt;/strong&gt;.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;a href=&quot;/images/entra_cognito/sso_attributes_claims.png&quot; target=&quot;_blank&quot;&gt;
            &lt;img src=&quot;/images/entra_cognito/sso_attributes_claims.png&quot; alt=&quot;User Attributes and Claims&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Set Unique User Identifier&lt;/strong&gt;
        &lt;ul&gt;
            &lt;li&gt;Select the &lt;strong&gt;Unique User Identifier (Name ID)&lt;/strong&gt; claim to edit it.&lt;/li&gt;
            &lt;li&gt;In the &lt;strong&gt;Source attribute&lt;/strong&gt;, set the value to &lt;code&gt;user.objectid&lt;/code&gt;.&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;Save the Changes&lt;/strong&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;a href=&quot;/images/entra_cognito/sso_object_id_claim.png&quot; target=&quot;_blank&quot;&gt;
            &lt;img src=&quot;/images/entra_cognito/sso_object_id_claim.png&quot; alt=&quot;Set Object ID Claim&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
        &lt;/a&gt;
    &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;update-the-aws-cognito-userpool&quot;&gt;Update the AWS Cognito userpool&lt;/h2&gt;
&lt;p&gt;Once you have defined all the claim mappings on the Entra ID side, it is time to connect the dots on AWS’s side.&lt;/p&gt;

&lt;h3 id=&quot;retrieve-saml-federation-metadata&quot;&gt;Retrieve SAML Federation Metadata&lt;/h3&gt;
&lt;p&gt;This is the intermediate step between configuring Entra ID and Cognito.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Get the Federation Metadata URL
    &lt;ul&gt;
      &lt;li&gt;In your Entra ID Enterprise application, navigate to the Single Sign-On section.&lt;/li&gt;
      &lt;li&gt;Locate the App Federation Metadata Url.&lt;/li&gt;
      &lt;li&gt;Copy this URL, as it will be needed in AWS Cognito.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/sso_metadata_url.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/sso_metadata_url.png&quot; alt=&quot;Federation Metadata URL&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;enable-the-idp&quot;&gt;Enable the IdP&lt;/h3&gt;
&lt;p&gt;After all configurations are done on Entra ID side, you need to update the configuration in Cognito.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Enable the IdP&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;In AWS Cognito, select the &lt;strong&gt;User Pool&lt;/strong&gt; and go to the &lt;strong&gt;Sign-in experience&lt;/strong&gt; tab.&lt;/li&gt;
      &lt;li&gt;Under the &lt;strong&gt;Federated identity provider sign-in&lt;/strong&gt; section, click on &lt;strong&gt;Add identity provider&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Select the &lt;strong&gt;SAML&lt;/strong&gt; type.&lt;/li&gt;
      &lt;li&gt;Enter a name under &lt;strong&gt;Provider name&lt;/strong&gt;.&lt;/li&gt;
      &lt;li&gt;Under &lt;strong&gt;Identifiers&lt;/strong&gt;, add the &lt;strong&gt;IdpIdentifier&lt;/strong&gt; value.&lt;/li&gt;
      &lt;li&gt;Use the recommended setting &lt;strong&gt;Require SP-initiated SAML assertions&lt;/strong&gt; for the &lt;strong&gt;IdP-initiated SAML sign-in&lt;/strong&gt; setting.&lt;/li&gt;
      &lt;li&gt;Enter the metadata document endpoint URL saved previously.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/sso_cognito_saml_config.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/sso_cognito_saml_config.png&quot; alt=&quot;Cognito SAML Configuration&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a better solution than uploading the XML file because Cognito refreshes the metadata every 6 hours or before the metadata expires. This way, you don’t have to manually refresh the metadata XML every time the Entra ID SSL certificates expire or any other change occurs on the Entra ID side that would impact the federation authentication.&lt;/p&gt;

&lt;h3 id=&quot;configure-attribute-mapping&quot;&gt;Configure Attribute Mapping&lt;/h3&gt;
&lt;p&gt;Configure the attributes that are stored in Entra ID and are mapped via the SAML schema in AWS Cognito. Here is a copy-and-paste friendly table for easier usage:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;SAML Attribute&lt;/th&gt;
      &lt;th&gt;User Pool Attribute&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&lt;/td&gt;
      &lt;td&gt;Profile&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;http://schemas.xmlsoap.org/claims/CommonName&lt;/td&gt;
      &lt;td&gt;Preferred User Name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname&lt;/td&gt;
      &lt;td&gt;Given Name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname&lt;/td&gt;
      &lt;td&gt;Family Name&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&lt;/td&gt;
      &lt;td&gt;Email&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/sso_cognito_attributes.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/sso_cognito_attributes.png&quot; alt=&quot;Cognito Attribute Mapping&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;enable-the-external-idp-for-app-clients&quot;&gt;Enable the External IdP for App Clients&lt;/h3&gt;
&lt;p&gt;Now that you have an IdP using the Entra ID configuration, you need to assign it to your application created in the Cognito userpool.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Enable the IdP for App Clients&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;In AWS Cognito, navigate to the &lt;strong&gt;App integration&lt;/strong&gt; tab, &lt;strong&gt;App client list&lt;/strong&gt; section.&lt;/li&gt;
      &lt;li&gt;Select the App client you want to configure and edit the &lt;strong&gt;Hosted UI&lt;/strong&gt; section.&lt;/li&gt;
      &lt;li&gt;From the &lt;strong&gt;Identity providers&lt;/strong&gt; dropdown, select your newly created IdP (e.g., EntraID) and save the changes.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;test-the-configuration&quot;&gt;Test the Configuration&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Click on the &lt;strong&gt;View Hosted UI&lt;/strong&gt; button to quickly test your changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;final-steps&quot;&gt;Final Steps&lt;/h3&gt;
&lt;p&gt;After a user has successfully authenticated via the external IdP, it will automatically be created in your Cognito userpool with the “Enabled” status. The confirmation status will be set to EXTERNAL_PROVIDER.&lt;/p&gt;

&lt;p&gt;The user attribute &lt;strong&gt;identities&lt;/strong&gt; will store the metadata relating to the external IdP that “owns” this identity. This includes the user’s ID in the external IdP’s attribute, in our case, the “Identifier (Entity ID)”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/entra_cognito/sso_cognito_identities.png&quot; target=&quot;_blank&quot;&gt;
    &lt;img src=&quot;/images/entra_cognito/sso_cognito_identities.png&quot; alt=&quot;Cognito Identities&quot; style=&quot;width: 85%; display: block; margin: 0 auto;&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These fields will be updated on each successful authentication, so you can rely on the fact that the fields you receive via JWT attributes will be up-to-date.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;By following these steps, you will have a fully functioning solution offering federated authentication with an external Entra ID. This setup ensures that user identities remain consistent and up-to-date, even when user attributes change.&lt;/p&gt;
</description>
        <pubDate>Tue, 08 Oct 2024 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/EntraID-SAML/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/EntraID-SAML/</guid>
      </item>
    
      <item>
        <title>Lazy Loading in C# (and not only)</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Lazy loading&lt;/em&gt; is a technique used to delay the execution of code for later on. There are a couple of reasons as to why you’d want to do that, and we’ll enumerate some:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Speed – the best code, is also the fastest code, the most secure and most maintainable… namely &lt;em&gt;no code at all&lt;/em&gt;. However, for obvious reasons, we do need at times to write code, but while we can’t avoid writing it, we could delay or even, &lt;em&gt;possibly&lt;/em&gt;, completely avoid &lt;em&gt;executing&lt;/em&gt; it.&lt;/li&gt;
  &lt;li&gt;Memory footprint – RAM is still important, avoiding loading a heavy object in memory does not just result in increased speed, but also in a more efficient system, memory-wise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;hands-on&quot;&gt;Hands-on&lt;/h2&gt;

&lt;p&gt;While the theory sounds great, there still remains the question of how do you actually &lt;em&gt;avoid&lt;/em&gt; calling code? While there are a couple of ways to do that, I’d like to focus on two of them:&lt;/p&gt;

&lt;h3 id=&quot;singletons&quot;&gt;Singletons&lt;/h3&gt;
&lt;p&gt;First, when it comes the the &lt;a href=&quot;https://en.wikipedia.org/wiki/Singleton_pattern&quot;&gt;&lt;em&gt;singleton pattern&lt;/em&gt;&lt;/a&gt;, objects (if we’re talking in an &lt;em&gt;OOP&lt;/em&gt; context, but this goes for other paradigms as well) are often &lt;em&gt;statically&lt;/em&gt; allocated. This affects us even more, as static code, often get initialized early on. For example in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET Framework&lt;/code&gt;  &lt;em&gt;static&lt;/em&gt; fields get initialized before the constructor of the class is called. This is also the case for other platforms, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Java&lt;/code&gt;. Here an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; check is usually employed for &lt;em&gt;lazy loading&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Singleton&lt;/span&gt; 
   &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Singleton&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_instance&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// redundant, for explicity&lt;/span&gt;
      
      &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Singleton&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      
      &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Singleton&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Singleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_instance&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;_instance&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Singleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example ☝️ we can see how an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; statement is used to check if our singleton has ever been instantiated before, if so, we just return that instance. However, if this is the first time, we create a first instance. This saves us some time and memory, because we might not get to use the class at all (depending on the use case) or we might just delay the execution.&lt;/p&gt;

&lt;p&gt;Notice, the more moder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET&lt;/code&gt; platform actually uses lazy loading by default. Static fields only get initialized before being used, and so, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET&lt;/code&gt; does this for us, as opposed to the older &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET Framework&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;instance-fields&quot;&gt;Instance fields&lt;/h2&gt;

&lt;p&gt;The more interesting and common use case is when we’d like to delay the initialization of an &lt;em&gt;instance&lt;/em&gt; field. For this, both frameworks (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET Framework&lt;/code&gt;) come with build-in support, namely: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lazy&amp;lt;T&amp;gt;&lt;/code&gt;, a generic class which can wrap objects of any type and delay their initialization. We’ll explore a simple implementation of such an idea, and see how we can achieve this in pretty much any programming language.&lt;/p&gt;

&lt;p&gt;Frist imagine the scenario of two classes:&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;with a &lt;em&gt;composition&lt;/em&gt; relationship of type:&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Foo&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;Bar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Our goal is to delay the initialization of the field &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bar&lt;/code&gt; in any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Foo&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;From the get go, our solution needs to address any possible type, not just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bar&lt;/code&gt;. This is why &lt;em&gt;generics&lt;/em&gt; are needed. So our solution begins to look as such:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Second, we’d need to know all about how &lt;em&gt;exactly&lt;/em&gt; to create this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bar&lt;/code&gt; object, and yet, &lt;em&gt;delay&lt;/em&gt; the process… This sounds like a &lt;em&gt;producer&lt;/em&gt; – a method that produces such and object, encorporates the &lt;em&gt;how&lt;/em&gt; – and a &lt;em&gt;callback&lt;/em&gt; (&lt;em&gt;lambda&lt;/em&gt; or &lt;em&gt;higher-order function&lt;/em&gt;), a method passed as argument, to be called when needed. This is exactly what we need! In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.NET&lt;/code&gt; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func&amp;lt;T&amp;gt;&lt;/code&gt; type holds a &lt;em&gt;method&lt;/em&gt; (or &lt;em&gt;function&lt;/em&gt; for you functional programmers) that takes no parameters and returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func&lt;/code&gt; comes in a buch of variations that also take arguments, the last one always representing the return value, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func&amp;lt;T,TResult&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Func&amp;lt;T,T,TResult&amp;gt;&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;We can use is as such:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_generator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;All that we’re providing to the constructor of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lazy&lt;/code&gt; class is a function, that describes how to return a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;, and whenever we want to actually return that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt;, we just call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Load&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s see this in action:&lt;/p&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Foo&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;c1&quot;&gt;// old code: Bar bar = new();&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar_lazy&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This looks great! Now, whenever we want our instance, we can just call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Load&lt;/code&gt; on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bar_lazy&lt;/code&gt;, but the beauty of warping things in a function is that, even if our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bar&lt;/code&gt; constructor required parameters, this would be fixed with just a small adjustment:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Foo&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;//ctor injection, provide `lazy_bar` as a dependency&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lazy_bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// calling code&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;lazy_bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;other_param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lazy_bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;It’s true that now we can’t really do an in-line initialization, but that would have been true even without our static wrapper, unless, of-course, we use static variables.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/lazy-loading-in-csharp/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/lazy-loading-in-csharp/</guid>
      </item>
    
      <item>
        <title>Improving security in AWS Cognito</title>
        <description>&lt;h1 id=&quot;a-call-for-debate-on-how-to-better-secure-aws-cognito&quot;&gt;A call for debate on how to better secure AWS Cognito&lt;/h1&gt;

&lt;p&gt;AWS Cognito is an identity management service for users who sign-up directly and for federated users who sign-in with external identity providers. It grants the ability to control access to web and mobile applications.&lt;/p&gt;

&lt;p&gt;The user handling is being done via the &lt;a href=&quot;http://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-with-cognito-user-pools.html&quot;&gt;&lt;strong&gt;User Pools&lt;/strong&gt;&lt;/a&gt; while the identities and assign permissions for users are configured inside the &lt;a href=&quot;http://docs.aws.amazon.com/cognito/latest/developerguide/getting-started-with-identity-pools.html&quot;&gt;&lt;strong&gt;Identity Pools&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over time, we found out that some Cognito user pools were deployed in their default configuration, making them possible honeypots.&lt;/p&gt;

&lt;h2 id=&quot;updating-users-via-the-public-cognito-api&quot;&gt;Updating users via the public Cognito API:&lt;/h2&gt;

&lt;p&gt;We discovered that the &lt;a href=&quot;https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html&quot;&gt;&lt;strong&gt;app client configuration&lt;/strong&gt;&lt;/a&gt; attributes are writable by default. As a result, if a user obtains a token with the &lt;em&gt;aws.cognito.signin.user.admin&lt;/em&gt; scope, they can modify a local user’s attributes via the Cognito public endpoint (https://cognito-idp.REGION.amazonaws.com) using the &lt;em&gt;x-amz-target&lt;/em&gt; header with the “CognitoIdentityProvider.UpdateUserAttributes” value. Moreover, a user can also delete its attributes or its account using the same approach.&lt;/p&gt;

&lt;p&gt;Imagine that some developers are not aware of this fact and use custom attributes for tenant separation or RBAC. An attacker can breach the tenant separation, access foreign user data, etc. by modifying these attributes (e.g. by changing attribute “custom:tenant: companyA” to “custom:tenant: companyB”).&lt;/p&gt;

&lt;h3 id=&quot;the-generic-solution&quot;&gt;The generic solution&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Remove the &lt;em&gt;aws.cognito.signin.user.admin&lt;/em&gt; from the app client scopes. Keep in mind that this does not solve the issue for public clients. When authenticating directly against the Cognito public endpoint (initiateAuth) with a user and password flow (or others), you always get a token with the &lt;em&gt;aws.cognito.signin.user.admin&lt;/em&gt; scope.&lt;/li&gt;
  &lt;li&gt;Remove the write-access permission in the app client configuration. Consider that this breaks in the case of federation with an external IdP because when a user signs in, Cognito updates the mapped attributes with the latest information from the IdP, even if its current value already matches the latest information.
This happens automatically in Cognito’s backend involving no public APIs. Due to this nature, your SAML logins won’t be affected by blocking the API calls discussed below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-custom-solution&quot;&gt;The custom solution&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Block undesired calls towards the Cognito public API using AWS WAF rules. Basically, you need to block API calls using the &lt;em&gt;x-amz-target&lt;/em&gt; header containing the “AWSCognitoIdentityProviderService.API_ACTION” string. A list of all APIs can be found here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pools-API-operations.html#user-pool-apis-auth-unauth-token-auth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read more about AWS WAF and Cognito here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-waf.html&lt;/p&gt;

&lt;p&gt;You might be thinking how do you know which API calls should you allow and which should you block. You’re in luck because we also faced the same issue. Our suggestion is to initially create a WAF rule in count mode which tracks all API calls made by your Cognito userpool towards the public endpoint, centralize the data and afterwards build a new WAF rule that blocks all API calls except the ones tracked by the first rule.&lt;/p&gt;

&lt;p&gt;Below is an example of a WAF rule that counts all the &lt;em&gt;AWSCognitoIdentityProviderService&lt;/em&gt; calls via the “x-amz-target” header:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Name&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Cognito-counting-calls-to-public-api&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Priority&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Statement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ByteMatchStatement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SearchString&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AWSCognitoIdentityProviderService&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;FieldToMatch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SingleHeader&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Name&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x-amz-target&quot;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TextTransformations&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Priority&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Type&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NONE&quot;&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;PositionalConstraint&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CONTAINS&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Action&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Count&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;VisibilityConfig&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SampledRequestsEnabled&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CloudWatchMetricsEnabled&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Cognito-counting-calls-to-public-api&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;

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

&lt;p&gt;The next step is to create a WAF Regex pattern set where you define the allowed calls tracked with the above rule. For this example we allowed only the &lt;em&gt;^AWSCognitoIdentityProviderService.InitiateAuth$&lt;/em&gt; and &lt;em&gt;^AWSCognitoIdentityProviderService.GetUser$&lt;/em&gt; patterns:&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-cognito-security/waf_regex.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Further you create the WAF rule that blocks all API calls initiated by your userpool towards the public API, except the patterns defined in the regex:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Name&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BLOCK-all-public-apis&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Priority&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Statement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;AndStatement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Statements&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NotStatement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Statement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;RegexPatternSetReferenceStatement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ARN&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;arn:aws:wafv2:REGION:ACCOUNT_NUMBER:regional/regexpatternset/REGEX_NAME/REGEX_ID&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;FieldToMatch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SingleHeader&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Name&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x-amz-target&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TextTransformations&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Priority&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Type&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NONE&quot;&lt;/span&gt;
                  &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SizeConstraintStatement&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;FieldToMatch&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SingleHeader&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Name&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;x-amz-target&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ComparisonOperator&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GT&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Size&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TextTransformations&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Priority&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Type&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NONE&quot;&lt;/span&gt;
              &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Action&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Block&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;VisibilityConfig&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SampledRequestsEnabled&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CloudWatchMetricsEnabled&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BLOCK-all-public-apis&quot;&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;

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

&lt;p&gt;Besides the regex reference statement, we added an AND condition using the “Field to match” for the &lt;em&gt;x-amz-target&lt;/em&gt; header
greater than 0. This is useful for scenarios where the &lt;em&gt;x-amz-target&lt;/em&gt; header is missing, allowing the calls to bypass our
WAF block rule (example: SAML login where the Hosted UI is used).&lt;/p&gt;

&lt;h2 id=&quot;account-takeover-via-unverified-emailphone&quot;&gt;Account takeover via unverified email/phone&lt;/h2&gt;

&lt;p&gt;Most of the user pools are configured with multiple login options, including email, username or phone. By default, the user pool option “Keep original attribute value active when an update is pending” is turned on. Make sure it stays like this to be protected.&lt;/p&gt;

&lt;p&gt;If the option “Keep original attribute value active when an update is pending” is not turned on and your application consuming a Cognito issued token does not check the &lt;em&gt;email_verified&lt;/em&gt; attribute but uses it directly to load the data/identify of a user, it will be exposed to a possbile takeover.&lt;/p&gt;

&lt;p&gt;An attacker can change the email attribute value of its own user to impersonate a victim’s email address, then login to an application using an alternative login option like username. The application processing the Cognito issued token will see the victim’s email address and use the unverified email attribute to load data/identify of the user.&lt;/p&gt;

&lt;h3 id=&quot;the-solution&quot;&gt;The solution&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure the &lt;em&gt;Keep original attribute value active when an update is pending&lt;/em&gt; setting is on in all your Cognito userpools.&lt;/li&gt;
  &lt;li&gt;Modify your applications to respect the &lt;em&gt;email_verified&lt;/em&gt; and &lt;em&gt;phone_number_verified&lt;/em&gt; claims.&lt;/li&gt;
  &lt;li&gt;If possible, modify your applications not to rely on modifiable attributes like email, username, etc. &lt;a href=&quot;https://openid.net/specs/openid-connect-core-1_0.html#ClaimStability&quot;&gt;&lt;strong&gt;Instead use the combination of immutable subject (sub) and issuer (iss) claims to identify a user&lt;/strong&gt;&lt;/a&gt;. You can find extra details about the Cognito ID Token Payload &lt;a href=&quot;https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;have-you-faced-any-of-the-scenarios-above-how-did-you-mitigate-the-issues-what-else-caught-your-attention-when-it-comes-to-securing-cognito&quot;&gt;Have you faced any of the scenarios above? How did you mitigate the issues? What else caught your attention when it comes to securing Cognito?&lt;/h3&gt;
&lt;p&gt; &lt;/p&gt;
</description>
        <pubDate>Fri, 12 May 2023 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/aws-cognito-security/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/aws-cognito-security/</guid>
      </item>
    
      <item>
        <title>Configure Centralised S3 bucket replication from multiple S3 bucket sources</title>
        <description>&lt;p&gt;In Haufe we are using AWS Organizations service with hundreds of accounts and multiple OUs, therefore it was a challenge for us to offer a centralised backup solution for files.&lt;/p&gt;

&lt;p&gt;AWS does not offer an out-of-box backup service for your files, so we needed to be creative.&lt;/p&gt;

&lt;p&gt;Considering this requirement, we realised that S3 bucket replication can be a good candidate in order to achive our goal. Of course, having a one-to-one replicated bucket solution does not scale, therefore we were thinking to create &lt;strong&gt;ONLY&lt;/strong&gt; one in the centralised replicated S3 bucket, which stores all the files from multiple S3 buckets sources.&lt;/p&gt;

&lt;p&gt;Amazon S3 replication enables automatic, asynchronous copying of objects across Amazon S3 buckets. Buckets that are configured for object replication can be owned by the same AWS account or by different accounts.&lt;/p&gt;

&lt;p&gt;There was one last challenge: how do we organise the centralised S3 bucket, in order to have a well structured folder/prefix for each source S3 bucket. The solution came from the centralised S3 bucket permission policy, where we used the &lt;a href=&quot;https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html&quot;&gt;${aws:PrincipalAccount}&lt;/a&gt; context key.&lt;/p&gt;

&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;

&lt;p&gt;In order to keep data encrypted at rest, you have to create at least 2 KMS keys, used by s3 to encrypt data :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;one in centralised account&lt;/li&gt;
  &lt;li&gt;one in each source account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From AWS console -&amp;gt; KMS -&amp;gt; Customer-managed keys -&amp;gt; Create key.
One important aspect is to create a valid kms policy, which allows AWS services to encrypt/decrypt data. Below you can see the kms policy for each type of key:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;KMS for centralised bucket, contains a condition based on OrganizationID, which allows all AWS accounts in our AWS Organization to use it&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/kms_centralised.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;KMS for each source AWS account&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/kms_source.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;create-s3-buckets&quot;&gt;Create S3 Buckets&lt;/h1&gt;

&lt;p&gt;We have to create 2 buckets one in each source account and another one in the centralised one.
From AWS console, go to S3 service and select Create bucket&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/create_policy.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Write the name of the source/centralised bucket&lt;/li&gt;
  &lt;li&gt;Enable Bucket version&lt;/li&gt;
  &lt;li&gt;Enable server side encryption and specify kms key created in the previous step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is valid &lt;strong&gt;ONLY&lt;/strong&gt; for centralised S3 bucket, in order to have a valid policy based on aws:PrincipalAccount and including Organization ID condition:&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/S3_policy.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;replication-is-configured-via-rules&quot;&gt;Replication is configured via rules.&lt;/h1&gt;

&lt;p&gt;You have to create a rule in each source S3 bucket to replicate objects to the centralised S3 bucket.
The bellow step is done only in sources AWS accounts:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Go to the Amazon S3 console&lt;/li&gt;
  &lt;li&gt;Click on the name of the &lt;strong&gt;source S3 bucket&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Click on the &lt;strong&gt;Management&lt;/strong&gt; tab&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Create replication rule&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Specify &lt;strong&gt;Replication rule name&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Leave &lt;strong&gt;Status&lt;/strong&gt; set to &lt;strong&gt;enabled&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Choose a rule scope select, &lt;strong&gt;Limit the scope of this rule using one or more filters&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Specify for prefix/folder the account id value, in order to be sync with centralised S3 bucked policy based on ${aws:PrincipalAccount}
        &lt;ul&gt;
          &lt;li&gt;eg. 123456789123 &amp;lt;- account id&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;For Destination select &lt;strong&gt;Specify a bucket in another account&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Specify the account id of the centralised S3 bucket&lt;/li&gt;
      &lt;li&gt;Specify the centralised S3 bucket name created in the prerequisites chapter&lt;/li&gt;
      &lt;li&gt;Check box &lt;strong&gt;Change object ownership to destination bucket owner&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;For &lt;strong&gt;IAM Role&lt;/strong&gt; leave &lt;strong&gt;Choose from existing IAM roles&lt;/strong&gt; selected, and select &lt;strong&gt;Create a new role&lt;/strong&gt; from the search results box
    &lt;ul&gt;
      &lt;li&gt;A new IAM role will be created, which has privileges to both S3 buckets and to KMS keys in both accounts&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;For &lt;strong&gt;Encryption&lt;/strong&gt;, choose &lt;strong&gt;Replicate objects encrypted with KMS keys&lt;/strong&gt; and select &lt;strong&gt;Enter AWS KMS Key ARN&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;For source objects check box the KMS key name created in the prerequisite step in source account&lt;/li&gt;
      &lt;li&gt;For destination objects get the KMS key ARN from the centralised account and paste in the field&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;For &lt;strong&gt;Destination Storage Class&lt;/strong&gt;, you can specify a destination storage class for objects replicated in centralised S3 bucket.&lt;/li&gt;
  &lt;li&gt;For &lt;strong&gt;Additional replication options&lt;/strong&gt;, I recommend to check box only &lt;strong&gt;Replication Time Control&lt;/strong&gt;, and do not select &lt;strong&gt;Delete marker replication&lt;/strong&gt;, since Delete markers created by S3 delete operations will be replicated in centralised S3 bucket&lt;/li&gt;
  &lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/rule_name.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/destination_role.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/encryption.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/storage_class.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/additional.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;test-replication&quot;&gt;Test replication&lt;/h1&gt;

&lt;p&gt;To test this rule you will upload an object into the source S3 bucket in  account id prefix  and observe that it is replicated into the centralised S3 bucket. For this step you will need a test file&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/upload.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/source.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;
&lt;img src=&quot;/images/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/replica.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As seen, the object uploaded to source S3 bucket in the &lt;strong&gt;account_id&lt;/strong&gt; prefix is replicated in centralised S3 bucket to the same prefix(account_id). 
This is the way you can configure a centralised S3 bucket for multiple source S3 buckets, splitting the source buckets based on the account_id prefix.&lt;/p&gt;

</description>
        <pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/configure-centralised-s3-bucket-replication-from-multiple-s3-source-buckets/</guid>
      </item>
    
      <item>
        <title>AWS Gameday</title>
        <description>&lt;h3 id=&quot;a-few-weeks-ago-our-company-hosted-an-aws-gameday-event-throughout-our-sites-there-arent-too-many-details-online-about-it-so-i-decided-to-give-you-a-short-heads-up&quot;&gt;A few weeks ago our company hosted an AWS Gameday event throughout our sites. There aren’t too many details online about it so I decided to give you a short heads-up.&lt;/h3&gt;

&lt;p&gt;If you get the chance to take part in it, don’t miss out. It’s really fun and in the end everyone agreed they learned something new.&lt;/p&gt;

&lt;p&gt;The teams will be created based on the individual AWS experience of the participants, trying to keep a balance and a sense of competitiveness. In our case, each team had 3 members passing from juniors to experienced users.&lt;/p&gt;

&lt;p&gt;Half an hour before the start you get all the details about the actual game, the entire “show” will be about 4-5 hours with no actual breaks. You can take a snack or a coffee break at any time but this will impact your teams ranking 😄&lt;/p&gt;

&lt;p&gt;During the activity there will be a live ranking system displayed on a local TV/large monitor where you can see how your team is performing. This might give you a sense of being on the Wall Street…&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-gameday/wolf-wall-street.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ll get access to AWS accounts with deployed infrastructure so prepairing with templates/scripts won’t help, it will just consume your time and you won’t have much, those 5 hours fly really fast.&lt;/p&gt;

&lt;p&gt;There are two ways to win points during the game. One is to configure and tweak your resources to be available as much as possible for other teams to use. The other is to consume data from other teams and this adds an extra spice to the entire gamble.&lt;/p&gt;

&lt;p&gt;Every now and then, some changes will be done to your existing infrastructure which will cause a bit of chaos. This is a great moment to be on your toes and climb the rakings.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-gameday/chaos-monkey.jpeg&quot; alt=&quot;&quot; style=&quot;width:80%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Try to delegate separate tasks to each member because details matter… 👀&lt;/p&gt;

&lt;p&gt;I’m not going to mention the services used because it would be a major spoiler but don’t worry, they are really common ones, so you’ll be just fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I wish you all the fun a geek can have and enjoy it!&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 10 Oct 2022 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/aws-gameday/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/aws-gameday/</guid>
      </item>
    
      <item>
        <title>Aws Sso From Adconnect To Azure Ad</title>
        <description>
</description>
        <pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/aws-sso-from-adconnect-to-azure-ad/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/aws-sso-from-adconnect-to-azure-ad/</guid>
      </item>
    
      <item>
        <title>Secure your website access with Kubernetes NGINX Ingress Controller, OAuth2 and Azure AD</title>
        <description>&lt;p&gt;Protecting resources behind a Kubernetes Ingress, is often NOT an easy task. Many applications do not provide built-in authentication out-of-the-box.
Therefore, creating an awesome user experience, including a single sign-on solution across all applications, can quickly become a tedious task.
In Haufe, we are using Kubernetes for a while now. One of our common practices with Kubernetes is using NGINX Ingress Controller for the main ingress controller whether we deploy it using EKS or AKS.&lt;/p&gt;

&lt;h1 id=&quot;how-does-it-work&quot;&gt;How does it work&lt;/h1&gt;

&lt;p&gt;We use the &lt;a href=&quot;https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#external-authentication&quot;&gt;NGINX Ingress Controller built-in functionality&lt;/a&gt; to define an external service to provide authentication for the ingress. Before passing a request to your app, the ingress will check whether the user is logged in or not by sending a request to proxy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/oauth2/auth&lt;/code&gt; endpoint and depending on its response it will pass the request or redirect the user to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/oauth2/start?rd=$escaped_request_uri&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;metadata:
  name: application
  annotations:
    nginx.ingress.kubernetes.io/auth-url: &quot;https://$host/oauth2/auth&quot;
    nginx.ingress.kubernetes.io/auth-signin: &quot;https://$host/oauth2/start?rd=$escaped_request_uri&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;NGINX Ingress Controller can be combined with oauth2_proxy to enable many OAuth providers like Google, AzureAD, GitHub and others. 
I am going to use &lt;a href=&quot;https://github.com/oauth2-proxy/oauth2-proxy&quot;&gt;OAuth2 Proxy&lt;/a&gt; together with the &lt;a href=&quot;https://github.com/kubernetes/ingress-nginx&quot;&gt;NGINX Ingress Controller&lt;/a&gt; to authenticate my Azure AD account against the Kibana website.&lt;/p&gt;

&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;EKS cluster (or any other type of Kubernetes cluster)&lt;/li&gt;
  &lt;li&gt;Global Administrator, Cloud Application Administrator or Application Administrator permissions into Azure AD in order to be able to create a new &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app&quot;&gt;App registration&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/elastic/helm-charts/tree/main/elasticsearch&quot;&gt;ElasticSearch cluster&lt;/a&gt; and &lt;a href=&quot;https://github.com/elastic/helm-charts/tree/main/kibana&quot;&gt;Kibana&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Helm3&lt;/li&gt;
  &lt;li&gt;kubectl&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;register-an-application&quot;&gt;Register an Application&lt;/h1&gt;

&lt;p&gt;Before you start, you need to register an application in Azure AD and create a client secret for it. 
You will need this for OAuth2 Proxy later, so write down the Application Client Id for later and make sure you enter a Redirect URI.
The callback URI for the OAuth2 Proxy is of the form &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://&amp;lt;your_company_domain&amp;gt;/oauth2/callback&lt;/code&gt;.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/registration-app-callback-url.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the next steps you will have to create a client secret and write it down for later use as well.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/create-client-secret.png&quot; alt=&quot;&quot; style=&quot;width:80%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;install-oauth2-proxy&quot;&gt;Install Oauth2 Proxy&lt;/h1&gt;

&lt;p&gt;The OAuth2 Proxy deployment manifest &lt;a href=&quot;/resources/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/oauth2-proxy.yaml&quot;&gt;oauth2-proxy.yaml&lt;/a&gt; needs some placeholders to be replaced using the values from the previous step:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    - name: OAUTH2_PROXY_CLIENT_ID
      value: &amp;lt;Application Client ID&amp;gt; # replace with client id
    - name: OAUTH2_PROXY_CLIENT_SECRET
      value: &amp;lt;Client Secret&amp;gt; # replace with client secret
    - name: OAUTH2_PROXY_COOKIE_SECRET
      value: &amp;lt;Random Secret&amp;gt; # replace with value of: python -c &apos;import os,base64; print(base64.b64encode(os.urandom(16)).decode(&quot;ascii&quot;))&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Also, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;Tenant ID&amp;gt;&lt;/code&gt; placeholder needs to be replaced with your Azure AD tenant id.&lt;/p&gt;

&lt;p&gt;After you defined the variables with the correct values, you can create the oauth2-proxy deployment&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Create namespace
kubectl create namespace demo-oauth2

# Make sure the variables above are available in the shell
kubectl apply -f oauth2-proxy.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;installing-the-nginx-ingress-controller&quot;&gt;Installing the NGINX Ingress Controller&lt;/h1&gt;

&lt;p&gt;You need to install NGINX Ingress-controller, in order to allow Kubernetes to understand Ingress objects.
Since I mentioned earlier that I used EKS from AWS, I need to setup NGINX Ingress Controller k8s service to use
Network Load Balancer.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# Installs NGINX Ingress Controller
helm install nginx ingress-nginx/ingress-nginx \
    --namespace demo-oauth2 \
    --set controller.service.annotations.&quot;service\.beta\.kubernetes\.io/aws-load-balancer-type&quot;=&quot;nlb&quot; \
    --set controller.service.annotations.&quot;service\.beta\.kubernetes\.io/aws-load-balancer-cross-zone-load-balancing-enabled&quot;=&quot;true&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;prepare-elasticsearch-cluster-and-kibana&quot;&gt;Prepare ElasticSearch cluster and Kibana&lt;/h1&gt;

&lt;p&gt;In order to have a valid example, you need to have a website, where you can verify authentication. My option was a 
Kibana application, but feel free to choose what fits your need.
Going further with this option, you have to install ElasticSearch and Kibana using helm charts&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Add the Elastic Helm charts repo
helm repo add elastic https://helm.elastic.co

# Install ElasticSearch 
helm install elasticsearch-demo-outh2 elastic/elasticsearch \
--namespace demo-oauth2 \ 
--set clusterName=demo-oauth2 \
--set replicas=1  \
--set minimumMasterNodes=1


# Install Kibana with Ingress object configured
helm install kibana-demo-outh2 elastic/kibana \
--namespace demo-oauth2 \
--set elasticsearchHosts=&quot;http://demo-oauth2-master:9200&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;deploy-ingress-objects&quot;&gt;Deploy Ingress objects&lt;/h1&gt;

&lt;p&gt;No, it was not a typo in the step title: you need to deploy 2 Ingress objects,
both of them pointing to the same host.&lt;/p&gt;

&lt;p&gt;First Ingress object needs to be annotated in such a way that it requires the user to authenticate against the second
Ingress’s endpoint and can redirect 401s to the same endpoint.&lt;/p&gt;

&lt;p&gt;The second Ingress objects exposes the oauth2-proxy service via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/oauth2&lt;/code&gt; path and handles the actual authentication.&lt;/p&gt;

&lt;p&gt;You can use the &lt;a href=&quot;/resources/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/kibana-ingress.yaml&quot;&gt;kibana-ingress.yaml&lt;/a&gt; example, but remember to replace the placeholders (for Ingress host, service name and port) with your own values.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Deploy ingress objects
kubectl apply -f kibana-ingress.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;test-your-setup&quot;&gt;Test your Setup&lt;/h1&gt;

&lt;p&gt;Once your NGINX endpoints come online, test the configuration by navigating to specific url in your web browser
Login with your credentials&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/SSO_username.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/SSO_password.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Access granted&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/welcome_kibana.png&quot; alt=&quot;&quot; style=&quot;width:80%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;recommendation&quot;&gt;Recommendation&lt;/h1&gt;

&lt;p&gt;Of course if you would like to setup a proper environment, I recommend to install &lt;a href=&quot;https://github.com/cert-manager/cert-manager&quot;&gt;cert-manager&lt;/a&gt;
helm chart, in order to generate valid certificates and activate HTTPS endpoints for your FQDN.&lt;/p&gt;

</description>
        <pubDate>Mon, 22 Aug 2022 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/secure-your-application-with-k8s-nginx-ingress-oauth2-azuread/</guid>
      </item>
    
      <item>
        <title>How to&amp;#58; login into Kibana/AWS OpenSearch using Azure AD</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/about-aws/whats-new/2020/10/amazon-elasticsearch-service-adds-native-saml-authentication-kibana/?nc1=h_ls&quot;&gt;AWS released native SAML authentication support for Kibana back in October 2020&lt;/a&gt; so we decided to give it a try and drop “the legacy” &lt;a href=&quot;http://work.haufegroup.io/aws-cognito-adfs/&quot;&gt;Cognito &amp;amp; ADFS integration&lt;/a&gt;. Our goal is to enable fine-grained access control into Kibana roles based on Azure AD groups.&lt;/p&gt;

&lt;h1 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;a deployed AWS OpenSearch stack&lt;/li&gt;
  &lt;li&gt;Global Administrator, Cloud Application Administrator or Application Administrator permissions into Azure AD in order to be able to create a new &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/add-application-portal&quot;&gt;Enterprise application&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;step-1&quot;&gt;Step 1&lt;/h1&gt;
&lt;p&gt;Go to the &lt;a href=&quot;https://portal.azure.com/&quot;&gt;Azure Portal&lt;/a&gt;, open the Azure AD service, go to Enterprise applications and select New application (Create your own application). The type of your new application should be “Integrate any other application you don’t find in the gallery (Non-gallery)”, type the name of your new application and create it (in this example, my app is called “Kibana login with Azure AD”).&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-create-ent-app-01.png&quot; alt=&quot;&quot; style=&quot;width:110%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-create-ent-app-02.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-2&quot;&gt;Step 2&lt;/h1&gt;

&lt;p&gt;Login into your AWS account, go to the OpenSearch service, select the Actions drop-down button, click on Modify authentication and select the Enable SAML authentication. From here, you will use the “Service provider entity ID” &amp;amp; “SP-initiated SSO URL” information in the next step. Scroll down to the “SAML master username (optional)” section and put your AD username (HawkeyeP@example.com) so you will be able to login as the master user and do the role mappings in Kibana, later at step 7.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/opensearch-saml-config-01.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-3&quot;&gt;Step 3&lt;/h1&gt;

&lt;p&gt;Go back in Azure AD to your Enterprise application, select the Single Sign-On option from the left menu and click on SAML as the single sign-on method. Edit the “Basic SAML Configuration” and add as the “Identifier (Entity ID)” the “Service provider entity ID” taken previously from OpenSearch service, then add as the “Reply URL (Assertion Consumer Service URL)” the “SP-initiated SSO URL” taken previously from OpenSearch service.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-saml-config-00.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-4&quot;&gt;Step 4&lt;/h1&gt;
&lt;p&gt;Configure the users/groups who have permissions to access your Enterprise application and then federate into your OpenSearch instance. Select the “Users and groups” option from the Enterprise application options, click on “Add user/group” and select who should have access to your application. In our example, we want to add an Azure AD group which will later be mapped into a role in Kibana (eg: active_directory_admin_group).&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-config-access.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-5&quot;&gt;Step 5&lt;/h1&gt;
&lt;p&gt;From the Single Sign-On option for your Enterprise application, edit the “User Attributes &amp;amp; Claims”, click on “Add a group claim”, select as claim the “Groups assigned to the application” option and the “Source attribute” as “sAMAccountName”.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-config-claims-00.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-config-claims.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This will send to your OpenSearch instance, as an attribute, the Azure AD groups you configured to have access to your Enterprise application. This information will then be used in “Step 6” to map the group name into a Kibana backend role.&lt;/p&gt;

&lt;p&gt;The last step in Azure AD is to download the Federation Metadata XML file and to upload it into your SAML configuration from the OpenSearch service.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/azure-ad-download-xml.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;REMEMBER: the Enterprise application uses a selfsigned certificate which is valid for 3 years - keep a record of this, in case you are running the setup in production.&lt;/p&gt;

&lt;h1 id=&quot;step-6&quot;&gt;Step 6&lt;/h1&gt;
&lt;p&gt;Login into your AWS account, go to the OpenSearch service, select the Actions drop-down button, click on Modify authentication and in the “SAML authentication for OpenSearch Dashboards/Kibana” section, click on “Import from XML file” and select the file you just downloaded in the previous step. You will see that the “IdP entity ID” section will auto update with the Azure Enterprise application ID. The last step is to add in the “Optional SAML settings - Roles key” section the “http://schemas.microsoft.com/ws/2008/06/identity/claims/groups” value.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/opensearch-optional-saml.png&quot; alt=&quot;&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;step-7&quot;&gt;Step 7&lt;/h1&gt;

&lt;p&gt;With the Azure AD user you added as “SAML master username (optional)” in Step 2, login into your OpenSearch application and map to a backend role, the Azure AD group you allowed to login into your Enterprise application in Step 4.&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/opensearch-security.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/opensearch-roles.png&quot; alt=&quot;&quot; style=&quot;width:70%&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;center&quot;&gt;&lt;img src=&quot;/images/aws-opensearch-kibana-azure-ad/opensearch-roles-mapping.png&quot; alt=&quot;&quot; style=&quot;width:120%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Congrats, you are now able to login into Kibana using an Azure AD group as a backend role.&lt;/p&gt;

</description>
        <pubDate>Fri, 17 Sep 2021 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/aws-opensearch-kibana-azure-ad/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/aws-opensearch-kibana-azure-ad/</guid>
      </item>
    
      <item>
        <title>Getting full transparency throughout your DevOps process</title>
        <description>&lt;h1 id=&quot;getting-full-transparency-throughout-your-devops-process&quot;&gt;Getting full transparency throughout your DevOps process&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DevOps&lt;/code&gt; is one of those terms, which is interpreted differently across our profession. Sometimes as a set of tools used for deployment automation, sometimes as the two guys in the basement, who administer our Kubernetes cluster. Much too often, we can observe the same pattern, as we did - and still do - with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agile&lt;/code&gt;: Using new terms while doing old things. Which leads to both, not gaining the benefits of the new approach, while keeping the known drawbacks from the old one.&lt;/p&gt;

&lt;p&gt;As it was for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agile&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DevOps&lt;/code&gt; should much rather be seen as a holistic approach, which only unleashes its full potential when its underlying ideas, principals, and mindset are understood and applied in our daily work&lt;sup&gt;&lt;a href=&quot;#manif&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. So I rather consider &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DevOps&lt;/code&gt; a consequent evolution of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agile&lt;/code&gt;: Empowering self-responsible teams to creating products in a customer-centric way. By eliminating time-consuming hand-overs or endless review processes before the first release to production. That is done leveraging a &lt;em&gt;shift-left&lt;/em&gt; approach. &lt;em&gt;Shift-left&lt;/em&gt; describes shifting both, responsibility and empowerment from the right side of a traditional software development workflow - manual user acceptance testing, security audits, deployment, monitoring, user-behavior analytics - to the left: an empowered development team, having proper skills to embed all those steps within its regular software development process.&lt;/p&gt;

&lt;h2 id=&quot;principal-devops-metrics&quot;&gt;Principal DevOps metrics&lt;/h2&gt;

&lt;p&gt;One of the key principles behind DevOps is bridging the gap between development and production. Increasing transparency across all stages of your product- and software development lifecycle can vastly help to build these bridges.&lt;/p&gt;

&lt;p&gt;This article concentrates on showing how to integrate metrics, which cover that whole lifecycle on one central dashboard, every team member has easy access to. This might be by having it on a big screen on the way to the coffee machine.&lt;/p&gt;

&lt;p&gt;The most central abstract DevOps metrics are the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Delivery Lead Time&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Deployment Frequency&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mean Time to Repair&lt;/code&gt; (MTTR), and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Change Fail Rate&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rep&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#rep&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hdb&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#hdb&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#acc&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;

&lt;p&gt;These Metrics are reflected and/or influenced by several other metrics.
One example would be open merge- or pull requests, which influence the first three of them.
The same would be for the time you need to fully build and deploy your product (end-to-end time of your CI/CD pipeline).
Whereas plenty of other metrics, which concentrate on your code’s quality, are likely to correlate to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Change Fail Rate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The following overview depicts the whole lifecycle, we are talking about. This article is about creating a central dashboard, which shows key metrics from all of these stages. Helping to improve the high-level overall DevOps metrics, described above.&lt;/p&gt;

&lt;p&gt;I am going to write some further articles, each covering some topics from this overview.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/devops_lifecycle.png&quot; alt=&quot;DevOps Lifecycle&quot; title=&quot;DevOps Lifecycle&quot; /&gt;
&lt;em&gt;Systematic presentation of all stages throughout the software development lifecycle. From writing code to running it in production.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;one-dashboard-for-the-whole-cycle&quot;&gt;One Dashboard for the whole cycle&lt;/h2&gt;

&lt;p&gt;Our dashboard combines data from these areas and sources:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open Merge Requests (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GitLab&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Software metrics: Code Quality and &lt;a href=&quot;https://en.wikipedia.org/wiki/Static_application_security_testing&quot;&gt;SAST&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SonarQube&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Status (and duration) of build pipelines for multiple repos (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GitLab&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Product version, which is deployed to any environment (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;custom API&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Estimated duration of a job queue (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jenkins&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Status and health of an Elasticsearch cluster (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elasticsearch&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;APM: Requests per minute, &lt;a href=&quot;https://en.wikipedia.org/wiki/Apdex&quot;&gt;Apdex score&lt;/a&gt;, Error rate, … (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NewRelic&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Analytics: Active users (also divided by instance / tenant) (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NewRelic&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/devops-dashboard-3.png&quot; alt=&quot;Our Dashboard&quot; title=&quot;Our Dashboard&quot; /&gt;
&lt;em&gt;The complete dashboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All elements on the dashboard aim at creating transparency about the key stages across the whole product lifecycle.
The overview of open merge requests, for instance, shall encourage developers to tackle them. Since code, which waits in this phase, does not add any value to our customers.&lt;/p&gt;

&lt;p&gt;The software metrics, including potential vulnerabilities, help to identify issues at the earliest stage possible. 
Since the earlier an issue is spotted, the quicker and cheaper it can be handled (as opposed to having QA, or worse, the customer find and report it). So these metrics shorten the feedback cycle.&lt;/p&gt;

&lt;h3 id=&quot;development&quot;&gt;Development&lt;/h3&gt;

&lt;p&gt;Instead of focusing on reducing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTBF&lt;/code&gt; (Mean Time between Failures) by extensive up-front testing and lengthy review processes, modern software development rather tends to focus on minimizing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MTTR&lt;/code&gt; (Mean Time to Repair). No matter how well your (manual) tests are, eventually your software will fail. Especially since not all of the real-world scenarios can be simulated during tests. So it might be wiser to “&lt;em&gt;prepare for the worst&lt;/em&gt;” instead of “&lt;em&gt;hoping for the best&lt;/em&gt;“&lt;sup&gt;&lt;a href=&quot;#elev&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Being able to quickly respond to errors in production is the most valuable asset. To address this, we visualize several metrics concerning our build pipelines: what is their success rate (reliability), how long do they take (speed) and how is that speed evolving over time (to prevent losing speed without noticing it).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/dev.png&quot; alt=&quot;Development related figures&quot; title=&quot;Development related figures&quot; /&gt;
&lt;em&gt;Development related figures: Software metrics coming from static code analysis, open merge requests and memory consumption of our Kubernetes clusters, where most of our build pipelines run into.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;build&quot;&gt;Build&lt;/h3&gt;

&lt;p&gt;Having several services running and aiming at a high deployment rate increases the need to know which version of your services are running at a given time. For that, we added a widget, which displays the currently deployed version across all our services and stages.&lt;/p&gt;

&lt;p&gt;After covering delivering new value as reliably and quickly as possible, you are interested in two things: first, is it working as expected and has no negative side effects on your application and, second, do your customer like it.&lt;/p&gt;

&lt;p&gt;To address these two questions, we added visualizations for response times and error rates. Allowing to quickly react, before any customer needs to pick up the phone to call the help desk.
Besides, we added some analytics to see the number of users currently using the application.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/pipeline.png&quot; alt=&quot;Build figures&quot; title=&quot;Build figures&quot; /&gt;
&lt;em&gt;Success rates of the last five builds and build duration over time. The color changes to yellow or red if certain thresholds are met.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;operation&quot;&gt;Operation&lt;/h3&gt;

&lt;p&gt;By the way, this also helps to visualize the impact we as a DevOps team have. We see actual users using what we have built. And we see how that affects response times and error rates. Opposed to implementing a feature, then having it to pass through several stages, including manual QA, eventually being deployed by the ops department. The next time we hear from it might be when pulling some associated bug ticket from the backlog some weeks later.&lt;/p&gt;

&lt;p&gt;Allowing developers to have impact and making this impact transparent, thus, is a great means to boost the team’s satisfaction and motivation. 
And there, the cycle closes. Since having a self-motivated and self-responsible team are the best preconditions to build great software.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/prod.png&quot; alt=&quot;Production figures&quot; title=&quot;Production figures&quot; /&gt;
&lt;em&gt;This part of the dashboard covers the application running in production: Number of users and requests, the Apdex Score and the error rate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our dashboard contains several views / pages. This comes in handy to prevent overloading a single page.
The dashboard automatically switches between these pages after a defined period (e.g. every 30 seconds). We will see how this is implemented in the next section.&lt;/p&gt;

&lt;p&gt;We used this mechanism in combination with the possibility to embed foreign Dashboards: We already had dashboards in various other tools, for instance &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Kibana&lt;/code&gt;. Whereas we also query data from the underlying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elastic Stack&lt;/code&gt; to show them in one of the shown widgets, it would not make sense to try to recreate all existing dashboards again. Instead, we are integrating them as they are.&lt;/p&gt;

&lt;p&gt;This one shows several aggregations for our production logs: Number of messages grouped by level, within each level, we aggregate for the classes, the messages come from. This easily enables us to see which classes produce the most errors or warnings.&lt;/p&gt;

&lt;p&gt;The date histogram quickly reveals unusual behavior, which helps us to react to possible issues before they hit the customers - or even better: react to warnings before they become problems at all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/devops-dashboard/kibana.png&quot; alt=&quot;Kibana&quot; title=&quot;Kibana&quot; /&gt;
&lt;em&gt;Integrated Kibana dashboard: Smashing has a special widget, which allows embedding iframes. Here we leverage this to embed a full-page Kibana dashboard.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;technical-implementation&quot;&gt;Technical implementation&lt;/h2&gt;

&lt;p&gt;As mentioned, since these metrics refer to different areas of the product lifecycle, they typically need to be gathered from different systems.
So we were not looking for a holistic monitoring solution, which contains all needed data (like e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Prometheus&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Grafana&lt;/code&gt;), but a dashboard, which can integrate data from different services and present them in a holistic view. Without permanently storing these data itself. This allows having an almost stateless tool, where we do not need to bother providing and maintaining some persistence solution.&lt;/p&gt;

&lt;p&gt;The dashboard we ended up with, is &lt;a href=&quot;https://smashing.github.io/&quot;&gt;Smashing&lt;/a&gt;, the successor of the no longer maintained &lt;a href=&quot;https://dashing.io/&quot;&gt;Dashing&lt;/a&gt; (thanks to Thomas Doerr, who introduced me to Dashing a while ago).&lt;/p&gt;

&lt;p&gt;Smashing allows placing widgets on a board, which fetch their data from arbitrary sources but presenting them in a unified way and a unified layout.
There is a &lt;a href=&quot;https://github.com/Smashing/smashing/wiki/Additional-Widgets&quot;&gt;whole bunch of widgets available&lt;/a&gt; to fetch data from certain services (e.g. &lt;em&gt;GitLab&lt;/em&gt;, &lt;em&gt;GitHub&lt;/em&gt;, &lt;em&gt;Google Analytics&lt;/em&gt;, &lt;em&gt;NewRelic&lt;/em&gt;, &lt;em&gt;Jira&lt;/em&gt;, …). Nevertheless, it is quite easy to write widgets to present data from any service, which provides some kind of API access.&lt;/p&gt;

&lt;h3 id=&quot;connecting-a-custom-source&quot;&gt;Connecting a custom source&lt;/h3&gt;

&lt;p&gt;Connecting to a custom data source is quite easy. Dashing/Smashing uses so-called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jobs&lt;/code&gt; to periodically fetch data from various sources and display the metrics on the board.
These &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jobs&lt;/code&gt; are Ruby files and are located within your dashboard’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jobs&lt;/code&gt; subfolder.&lt;/p&gt;

&lt;p&gt;The relevant parts of our job, which fetches data from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SonarQube&lt;/code&gt; looks like this:&lt;/p&gt;

&lt;p&gt;Since we have different jobs for different repositories / applications, we extracted some commonly used methods into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sonar_methods.rb&lt;/code&gt;.
These methods are included in our actual job:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require_relative&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;sonar_methods&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Afterward, we read some configuration parameters from the job’s configuration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sonar.cfg&lt;/code&gt;. For instance, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SonarQube&lt;/code&gt; server URL, the widget’s id, where we will show the results and the interval in which the data gets refreshed.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Load configuration file&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;config_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sonar.cfg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/(\S+)\s*=\s*&quot;([^&quot;]+)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Read parameters&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;server&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;widget_id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;interval&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;

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

&lt;p&gt;Sensitive configuration parameters like credentials are read from the environment resp. a local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file to not have them checked into source control:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;SONARQUBE_ACCESS_TOKEN&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;MISSING SONARQUBE_ACCESS_TOKEN variable (check your .env file)!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;SONARQUBE_ACCESS_TOKEN&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TIP: In an upcoming blog post, I am going to show how to keep &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Secrets-as-Code&lt;/code&gt;, having them properly encrypted using &lt;a href=&quot;https://github.com/mozilla/sops&quot;&gt;SOPS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The job gets executed within the Scheduler:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;SCHEDULER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;every&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:first_in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Get data&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getIssueCountBySeverities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;severities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getFurtherMetrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# Pass the retrieved data to a widget on the dashboard&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;send_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;items: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_event&lt;/code&gt; publishes the data to the widget, referenced by its id.&lt;/p&gt;

&lt;p&gt;That’s all related to the general setup, scheduling, and execution of the job.
How to fetch the data from the corresponding services differs according to their APIs. Here are the methods we use to fetch data from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SonarQube&lt;/code&gt; (the included &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sonar_methods.rb&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;net/http&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;json&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;openssl&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;uri&apos;&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@metricLabels&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;s2&quot;&gt;&quot;bugs&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;BUGS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;vulnerabilities&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;VULNERABILITIES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;code_smells&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;SMELLS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;coverage&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;COVERAGE (%)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;duplicated_lines_density&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;CODE DUPLICATION (%)&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getIssueCountBySeverities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;severities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;issuesBaseUri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/issues/search?componentKeys=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%3A&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;statuses=OPEN&amp;amp;severities=&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;severitiesArray&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;severities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;,&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;severitiesArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;severity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;issuesBaseUri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;severity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request_uri&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;basic_auth&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:use_ssl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scheme&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verify_mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OpenSSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VERIFY_NONE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;severity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;total&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getFurtherMetrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/api/measures/component_tree?component=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groupId&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%3A&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;artifactId&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;metricKeys=bugs,code_smells,coverage,vulnerabilities,duplicated_lines_density&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request_uri&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;basic_auth&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:use_ssl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scheme&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:verify_mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OpenSSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SSL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VERIFY_NONE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;metricData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
    
    &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;baseComponent&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;measures&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;metricData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;metric&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;value&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;vi&quot;&gt;@metricLabels&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:label&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metricData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;embedding-already-rendered-widgets&quot;&gt;Embedding already rendered widgets&lt;/h3&gt;

&lt;p&gt;Besides the possibility to render widgets from source data, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Smashing&lt;/code&gt; also allows integrating already rendered widgets. This is done using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-view=&quot;Iframe&quot;&lt;/code&gt;. We are using this to embed a full page Kibana dashboard (using max values for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-sizex&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data-sizey&lt;/code&gt;):&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;gridster&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-row=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-col=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-sizex=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;7&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-sizey=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;logs_grouped&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-view=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Iframe&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data-src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://uri/app/kibana#/dashboard/baba891a-b0a9-11ey-879b-7de326663eab?embed=true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TIP: The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;embed=true&lt;/code&gt; URL parameter leads to hiding unnecessary control elements, like the search bar. So it automatically opens it in full-screen mode.&lt;/p&gt;

&lt;h3 id=&quot;automatically-browse-through-several-dashboards&quot;&gt;Automatically browse through several dashboards&lt;/h3&gt;

&lt;p&gt;As shown above, one quickly has more meters than fit on one screen. To keep your dashboards neat and clear - allowing spotting the most important elements at first glance - you can consider splitting the meters to various dashboards.&lt;/p&gt;

&lt;p&gt;There is a &lt;a href=&quot;https://github.com/vrish88/sinatra_cyclist&quot;&gt;plugin&lt;/a&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Smashing&lt;/code&gt;, which automatically browses through a defined list of dashboards in a defined period.&lt;/p&gt;

&lt;p&gt;Having the plugin installed and configured, you can start the browsing mode by appending &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/_cylce&lt;/code&gt; to your dashboard’s URL. The period can be passed via an URL parameter. This example would switch the board every 30 seconds:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://your.dashboard:3030/_cycle?duration=30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;securing-access&quot;&gt;Securing access&lt;/h3&gt;

&lt;p&gt;Depending on where you run the dashboard and which data it contains, you may want to restrict access to it.
That can be done i.e. via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BasicAuth&lt;/code&gt;. Within Dashing’s configuration (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.ru&lt;/code&gt;) you’ll find a placeholder for it:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# [...]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;helpers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;protected!&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Put any authentication code you want in here.&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# This method is run before accessing any resource.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To activate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BasicAuth&lt;/code&gt;, using credentials &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER_NAME&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USER_PASS&lt;/code&gt; from the environment, adjust it like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;dashing-contrib&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;dashing&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;DashingContrib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# [...]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;helpers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;protected!&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorized?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;WWW-Authenticate&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%(Basic realm=&quot;Restricted Area&quot;)&lt;/span&gt;
        &lt;span class=&quot;kp&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:halt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Not authorized&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;authorized?&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;Rack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Basic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;provided?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;basic?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;credentials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@auth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;credentials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;USER_NAME&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;USER_PASS&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dockerize-the-dashboard&quot;&gt;Dockerize the Dashboard&lt;/h3&gt;

&lt;p&gt;Finally, we dockerized the dashboard. Allowing us to deploy it to a local workstation, which is attached to a big screen, or to deploy it to some cloud service - which for sure is needed if you have distributed teams. Or, like these days, home-office becomes more important.&lt;/p&gt;

&lt;h2 id=&quot;make-technology-and-culture-reinforce-each-other&quot;&gt;Make technology and culture reinforce each other&lt;/h2&gt;

&lt;p&gt;Making the complete software development lifecycle transparent to the whole team brings a lot of benefits.
Besides spotting possible bottlenecks, quality-, security- or performance issues, it shows the team the impact it has.&lt;/p&gt;

&lt;p&gt;That’s a great way of supporting a team’s satisfaction, self-awareness and self-responsibility.&lt;/p&gt;

&lt;p&gt;By that, technology helps to support the culture, whereas culture leads to improved technology. This mutually reinforcing cycle is the best basis for building great software.&lt;/p&gt;

&lt;h2 id=&quot;endnotes-and-literature&quot;&gt;Endnotes and Literature&lt;/h2&gt;

&lt;p&gt;&lt;a name=&quot;manif&quot;&gt;1&lt;/a&gt;: Aiming in that direction, Jez Humble, author of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continuous Delivery (2010)&lt;/code&gt;, felt the urge starting to work on a &lt;a href=&quot;https://sites.google.com/a/jezhumble.net/devops-manifesto/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DevOps Manifesto&lt;/code&gt;&lt;/a&gt;, which stresses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DevOps&lt;/code&gt; to be rather a “&lt;em&gt;philosophy&lt;/em&gt;” and a “&lt;em&gt;cultural, professional movement with attitude and values&lt;/em&gt;” rather than “&lt;em&gt;a role, a set of tools&lt;/em&gt;” or “&lt;em&gt;a prescriptive process&lt;/em&gt;”.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;rep&quot;&gt;2&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rep&lt;/code&gt;: &lt;a href=&quot;https://puppet.com/resources/&quot;&gt;State of DevOps Report&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;hdb&quot;&gt;3&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hdb&lt;/code&gt;: Gene Kim, Patrick Debois, Jez Humble, John Willes: The DevOps Handbook: How to Create World-Class Agility, Reliability, and Security in Technology Organizations, 2016.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;acc&quot;&gt;4&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;acc&lt;/code&gt;: Gene Kim, Jez Humble, Nicole Forsgren: Accelerate. The Science of Lean Software and DevOps. Building and Scaling High Performing Technology Organizations, 2018.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;elev&quot;&gt;5&lt;/a&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;elev&lt;/code&gt;: Gregor Hohpe: The Software Architect Elevator: Redefining the Architect’s Role in the Digital Enterprise, 2020.&lt;/p&gt;

</description>
        <pubDate>Mon, 07 Dec 2020 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/devops-dashboard/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/devops-dashboard/</guid>
      </item>
    
      <item>
        <title>Let&apos;s Scale - Part 2!</title>
        <description>&lt;h2 id=&quot;part-2-keep-your-infrastructure-code-clean&quot;&gt;Part 2: Keep your Infrastructure Code Clean!&lt;/h2&gt;

&lt;h3 id=&quot;retro&quot;&gt;Retro&lt;/h3&gt;

&lt;p&gt;In &lt;a href=&quot;link&quot;&gt;part-1&lt;/a&gt; we took a short look on Haufe`s cloud journey and how Terragrunt can boost your Terraform modularization by use DRY Terraform code. In the this part of the series, we take a look which tools from the Terraform community could support your daily work to keep your Terraform code &lt;a href=&quot;https://www.amazon.de/Clean-Architecture-Robert-Martin-2016-12-10/dp/B01N2GDUQ9&quot;&gt;clean&lt;/a&gt;, smiliar to keeping your app code clean.&lt;/p&gt;

&lt;h3 id=&quot;generate-automatically-documentation-with-terraform-doc&quot;&gt;Generate automatically Documentation with Terraform-Doc&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I get from it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/segmentio/terraform-docs&quot;&gt;TF-Docs&lt;/a&gt; generates and updates documentation for Terraform modules automatically, for input and output variables. This helps when you decide to split Terraform code ( something you can do with Terragrunt ) into a lot of small composable modules. You can choose between various output formats like markdown text or tables. Therefore you can just add it to your readme.md files in your repository for each module. With that TF-Docs is a consistent shortcut to keep your module documentation up to date on the same place where the code is, without writing it.&lt;/p&gt;

&lt;p&gt;This is how it looks like as a sample markdown table:
&lt;img src=&quot;../images/terraform_docs.png&quot; alt=&quot;TF-Doc Markdown Table&quot; width=&quot;100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TF-Docs is a utility tool written in Golang which is available by MIT license. After local download you can run it via bash, or how we see later, add it via pre-commit to your git workflow.&lt;/p&gt;

&lt;p&gt;It supports terraform v0.12.&lt;/p&gt;

&lt;p&gt;Install it with brew or linuxbrew*:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;terraform-docs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;If you use Windows, &lt;a href=&quot;https://docs.microsoft.com/de-de/windows/wsl/install-win10&quot;&gt;WSL&lt;/a&gt; is an easy way to use it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Run it this way to get a markdown text:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform-docs markdown &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Run it this way to get markdown table:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;terraform-docs markdown table &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TF-Docs prints the description of your variables and outputs, as following sample shows:&lt;/p&gt;

&lt;p&gt;This code …:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;variable &lt;span class=&quot;s2&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  description &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Environment tag, e.g prod&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

variable &lt;span class=&quot;s2&quot;&gt;&quot;region&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  description &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;WS Region, e.g eu-central as default&quot;&lt;/span&gt;
  default &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;eu-central-1&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

output &lt;span class=&quot;s2&quot;&gt;&quot;public_ip&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  value &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;aws_eip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.bastion.public_ip&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  description &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bastion hosts external IP address&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… outputs to this documentation:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# This is a Sample Readme&lt;/span&gt;

Lorem ipsum dolor sit amet, cum sint soluta instructior ut, eleifend efficiantur eam &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Cu tota splendide &lt;span class=&quot;nb&quot;&gt;sed&lt;/span&gt;, mel discere appellantur cu.

&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;## Inputs&lt;/span&gt;

| Name | Description | Type | Default | Required |
|------|-------------|:----:|:-----:|:-----:|
| environment | Environment tag, e.g prod | string | - | &lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt; |
| region | AWS Region, e.g eu-central as default |eu-central-1 | string | - | &lt;span class=&quot;nb&quot;&gt;yes&lt;/span&gt; |

&lt;span class=&quot;c&quot;&gt;## Outputs&lt;/span&gt;

| Name | Description |
|------|-------------|
| public_ip | Bastion hosts external IP address.|

&amp;lt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; END OF PRE-COMMIT-TERRAFORM DOCS HOOK &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What I have to consider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I personally think it is a best practise to add it as a githook to your git workflow, to use it in a consistent way. If you are a small team, you may not benefit as much from it as you would if you are platform team which shares the code with multiple teams. Also you could say, Terraform for itself is always an always-up-to-date document. Nevertheless with .md integration it works nicely!&lt;/p&gt;

&lt;h3 id=&quot;run-security-scans-with-terraform-sec&quot;&gt;Run Security Scans with Terraform-Sec&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I get out of it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/liamg/tfsec&quot;&gt;TF-Sec&lt;/a&gt; performs vulnerability tests for your Terrafrom configuration in the following way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Checks for sensitive data inclusion across all providers&lt;/li&gt;
  &lt;li&gt;Checks for violations of AWS, Azure and Google security best practice recommendations&lt;/li&gt;
  &lt;li&gt;Scans modules (currently only local modules are supported)&lt;/li&gt;
  &lt;li&gt;Evaluates expressions as well as literal values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete provider specific tests are listed &lt;a href=&quot;https://github.com/liamg/tfsec#included-checks&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool is written in Golang and supports terraform v0.12. Install it with brew or linuxbrew. If you use Windows the &lt;a href=&quot;https://docs.microsoft.com/de-de/windows/wsl/install-win10&quot;&gt;WSL&lt;/a&gt; is a easy way to run bash commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew tap liamg/tfsec
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;liamg/tfsec/tfsec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It works by scanning your directory recursively by running it on your terminal or the terminal from your ci/cd tool. You can choose between varrious output formats like JSON or jUnit.&lt;/p&gt;

&lt;p&gt;Below you will find an example issue with S3. TF-Sec gives you a list of all findings, an explination per finding, the issue id and a link to get more information about each listed issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Code:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_s3_bucket&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_secure_bucket&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    bucket &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_bucket&quot;&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;TFSEC Report:&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Problem 1
      
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;AWS017][ERROR] Resource &lt;span class=&quot;s1&quot;&gt;&apos;aws_s3_bucket.my_secure_bucket&apos;&lt;/span&gt; defines an unencrypted S3 bucket &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;missing server_side_encryption_configuration block&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

      /tfsec_test/s3_bucket.tf:1-3

           1 | resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_s3_bucket&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_secure_bucket&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
           2 |   bucket &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_bucket&quot;&lt;/span&gt;
           3 | &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
           4 | 

      See https://github.com/liamg/tfsec/wiki/AWS017 &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.
      
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also choose to &lt;a href=&quot;https://github.com/liamg/tfsec#ignoring-warnings&quot;&gt;ignore&lt;/a&gt; specific issues by adding a flag to your Terraform code.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_s3_bucket&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_secure_bucket&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    bucket &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_bucket&quot;&lt;/span&gt; 
     &lt;span class=&quot;c&quot;&gt;#tfsec:ignore:AWS017&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Additionally, you can also &lt;a href=&quot;https://github.com/liamg/tfsec#disable-checks&quot;&gt;disable&lt;/a&gt; specific checks for all modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I have to consider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run this tool and have fixed the promoted issues, it does not mean you are fully protected afterwards, which is clear. But you can add it as an additional step to your CI/CD pipeline with the single command  “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tfsec .&lt;/code&gt;” and you then have then made the world a bit safer place ;)&lt;/p&gt;

&lt;h3 id=&quot;keep-your-terraform-code-clean-with-terraform-lint&quot;&gt;Keep your Terraform Code Clean with Terraform-Lint&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I get from it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On your IDE or in an editor like VS Code, you can snap in Terraform-Lint for several programming languages as well as for IaC syntaxes like the Hashicorp language HCL linters. The good thing about &lt;a href=&quot;https://github.com/terraform-linters/tflint&quot;&gt;TF-LINT&lt;/a&gt; is, it is a part of the other open source tools shown here in the article, and you can integrate it as a githook the same way as the other tools. Main advantagie is that it validates provider specific issues which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;terraform/terragrunt validate, plan or apply&lt;/code&gt; doesn`t find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No suprise, this is like all other tools written in Golang and supports terraform v0.12. You can install it with brew or linuxbrew:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;tflint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TF-Lint inspects all configurations under the current directory by default:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tflint &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It has rules for Azure as well as for AWS: AWS rules have been further developed by the community. &lt;a href=&quot;https://github.com/terraform-linters/tflint/tree/master/docs/rules&quot;&gt;Here&lt;/a&gt; you find a list of the rules. Below, you get a sample for how it works when you run it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Code:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_route&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my_route_table&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  route_table_id         &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rtb_for_something&quot;&lt;/span&gt;
  destination_cidr_block &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;10.0.1.0/16&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;TFLINT Report:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 issue&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; found:

Error: The routing target is not specified, each aws_route must contain either egress_only_gateway_id, gateway_id, instance_id, nat_gateway_id, network_interface_id, transit_gateway_id, or vpc_peering_connection_id. &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;aws_route_not_specified_target&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

  on template.tf line 1:
   1: resource &lt;span class=&quot;s2&quot;&gt;&quot;aws_route&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

Reference: https://github.com/terraform-linters/tflint/blob/v0.11.0/docs/rules/aws_route_not_specified_target.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can run it as well as all the other tools locally, or in your pipeline as an additional stage like following blueprint example shows:&lt;/p&gt;

&lt;p&gt;.gitlab-ci.yaml:&lt;/p&gt;
&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lint&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;terralint&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lint&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$CI_REGISTRY/my_cicd_image&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tflint .&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-runner&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What I have to consider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TF-Sec support specific TF versions in certain &lt;a href=&quot;https://github.com/terraform-linters/tflint/blob/master/docs/guides/compatibility.md&quot;&gt;ways&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;integrate-all-the-terraform-helpers-in-one-to-your-git-workflow-with-pre-commit&quot;&gt;Integrate all the Terraform Helpers in One to your Git Workflow with Pre-Commit&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What I get from it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://pre-commit.com&quot;&gt;Pre-commit&lt;/a&gt; enables you to add all the listed utility tools to your git workflow. More Terraform integrated tools can be found as ready-to-use githooks on github within this project &lt;a href=&quot;https://github.com/antonbabenko/pre-commit-terraform/blob/master/README.md&quot;&gt;pre-commit-terraform&lt;/a&gt;. Besides the tools I already explained, pre-commit-terraform also uses the default Terraform function with &lt;em&gt;“terraform fmt”&lt;/em&gt; for formatting and &lt;em&gt;“terraform validate”&lt;/em&gt; for validatation as well as the same for Terragrunt.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://pre-commit.com/hooks.html&quot;&gt;Here&lt;/a&gt; you see the complete list of general available githooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To install the pre-commit tool and all the Terrform helpers as dependencies run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pre-commit gawk terraform-docs tflint tfsec coreutils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To install the pure pre-commit run following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pre-commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After installation you can add a ‘.pre-commit-config.yaml’ for pre-commit configuration to your local git root and configure each tool. Following sample lists the tools configuration from this article:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;repos&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;git://github.com/antonbabenko/pre-commit-terraform&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;rev&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;v1.30.0&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hooks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_docs&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Terraform docs&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Inserts input and output documentation into README.md (using terraform-docs).&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;require_serial&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_docs.sh&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;--args=--with-aggregate-type-defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;script&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;(\.tf)$&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;\.terraform\/.*$&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_tfsec&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Terraform validate with tfsec&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Static analysis of Terraform templates to spot potential security issues.&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_tfsec.sh&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;script&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_tflint&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Terraform validate with tflint&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Validates all Terraform configuration files with TFLint.&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;terraform_tflint.sh&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;script&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;(\.tf|\.tfvars)$&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;exclude&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;\.terraform\/.*$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Afterwards, you can run pre-commit with the specific command in your terminal as a test. It is also executed in the background before you commit or push as soon as you have added it as a githook. Below you see a sample screenshot of how the report looks like after execution:
&lt;img src=&quot;../images/Pre_Commit_Terraform_Sample.png&quot; alt=&quot;TF-Sec report&quot; width=&quot;100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I have to consider?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need to like to work with terminal tools and the git command line: I really believe that you can boost your collaboration for IaC, by using these little helpers.&lt;/p&gt;

&lt;h3 id=&quot;testing-your-terraform-modules-as-units&quot;&gt;Testing your Terraform Modules as Units&lt;/h3&gt;

&lt;p&gt;I haven’t had time to take a deep look into it yet, but if you heavily rely on Terraform, the community ecosystem also has Unit-Test-Frameworks to offer. Terraform offers to write Golang unit tests for custom &lt;a href=&quot;https://www.terraform.io/docs/extend/testing/unit-testing.html&quot;&gt;plugins&lt;/a&gt;, &lt;a href=&quot;https://github.com/gruntwork-io/terratest&quot;&gt;Terratest&lt;/a&gt; enables you to write Golang unit tests for your Terraform code and include Docker, Helm, and more. Terratest can also be added as a githook[https://github.com/gruntwork-io/terratest/blob/master/.pre-commit-config.yaml] or to your ci/cd. As an alternative, &lt;a href=&quot;https://newcontext-oss.github.io/kitchen-terraform/getting_started.html&quot;&gt;kitchen-terraform&lt;/a&gt; is a Ruby based test framework.&lt;/p&gt;

&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;

&lt;p&gt;In this series, we have seen how Terraform community tools can expand your IaC ecosystem and boost your scalability. Terragrunt helps you to get DRY code for your Terraform modules, which can be useful if you think about sharing, reusing and providing infrastructure. Pre-commit-terraform gives you the ability to enhance security, linting, testing and documentation within your git workflow for your team or teams. In addition, the tools are easy to add to your ci/cd pipeline, like we saw with the example of Terragrunt and Terraform-Lint, by adding a Docker Container to your pipeline and running a single bash command.&lt;/p&gt;

&lt;p&gt;If you are responsible for one monolthic product with only 2 environments like dev and prod, Terragrunt has less impact on your scalability. It is different, if you share IaC for multiple environments, services, teams and accounts. If and how much these tools helping you to scale also depends on how heavily you work with Terraform and whether you view IaC as a critical part of your software development skill portfolio.&lt;/p&gt;
</description>
        <pubDate>Sat, 04 Jul 2020 00:00:00 +0000</pubDate>
        <link>http://work.haufegroup.io/part2-terraform-precommit/</link>
        <guid isPermaLink="true">http://work.haufegroup.io/part2-terraform-precommit/</guid>
      </item>
    
  </channel>
</rss>