<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[hayao-k.dev]]></title><description><![CDATA[AWS Community Builder / 12x AWS Certified / Feel free to follow me!]]></description><link>https://hayao-k.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 16:28:56 GMT</lastBuildDate><atom:link href="https://hayao-k.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Effortlessly Export AWS Health Organizational View to CSV with This CLI Tool]]></title><description><![CDATA[💡
The tools discussed in this article leverage the AWS Health API, which requires a Business or higher-level AWS Support plan.


Introduction
For all AWS Organizations administrators worldwide, how do you handle the events notified by AWS Health? I ...]]></description><link>https://hayao-k.dev/effortlessly-export-aws-health-organizational-view-to-csv-with-this-cli-tool</link><guid isPermaLink="true">https://hayao-k.dev/effortlessly-export-aws-health-organizational-view-to-csv-with-this-cli-tool</guid><category><![CDATA[AWS]]></category><category><![CDATA[AWS Organizations]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[#AWSHealth]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Thu, 16 May 2024 15:21:55 GMT</pubDate><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The tools discussed in this article leverage the AWS Health API, which requires a Business or higher-level AWS Support plan.</div>
</div>

<h2 id="heading-introduction">Introduction</h2>
<p>For all AWS Organizations administrators worldwide, how do you handle the events notified by AWS Health? I imagine you receive numerous notifications daily when utilizing AWS services.</p>
<p>These events need to be properly managed as they can have a significant impact on the availability and reliability of your systems. Recently, there have been significant events scheduled, such as the Amazon RDS certificate update in August and the end of support for the AWS Lambda Python 3.8 runtime in October.</p>
<p>If you're managing multiple accounts within your organization, manually checking events and gathering relevant information can take time and effort.</p>
<p>I have developed a CLI tool called AWS Health Exporter to address this challenge.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/aws-health-exporter">https://github.com/hayao-k/aws-health-exporter</a></div>
<p> </p>
<h2 id="heading-key-features">Key Features</h2>
<p>AWS Health Exporter is a command-line tool for retrieving event information from the organizational view of AWS Health. It allows you to filter events by service name, status, and more and export details of the relevant accounts and resource IDs to a CSV file.</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/3c7933a4-8505-b172-f8ed-8d4ab3ac5a73.png" alt /></p>
<ul>
<li><p><strong>AWS Organizations Support:</strong> Retrieves information from the organizational view of AWS Health. It cannot be used with standalone accounts, but there is an option to output data for a single account only.</p>
</li>
<li><p><strong>CSV Export:</strong> Data is formatted and exported in CSV format, making it easy to save, share, and analyze.</p>
</li>
<li><p><strong>Event Filtering:</strong> Filters events by conditions such as service name and status, making it easier to find the events you're looking for.</p>
</li>
<li><p><strong>Resource Filtering:</strong> Only retrieves resources matching specific status codes (IMPAIRED, UNIMPAIRED, UNKNOWN, PENDING, or RESOLVED).</p>
</li>
</ul>
<h2 id="heading-about-aws-health-organizational-view">About AWS Health Organizational View</h2>
<p>Enabling the organizational view allows you to aggregate AWS Health events for all accounts within the organization. Data is retained for 90 days, and users/roles of the organization's management or delegated administrator accounts can access the information.</p>
<p>You can set it up and refer to it from "Your organization health" in the AWS Health dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715870555014/742b551f-2aba-4805-856d-1267b9f2e88d.png" alt class="image--center mx-auto" /></p>
<p>In the organizational view, you can check information for each event, such as:</p>
<ul>
<li><p>Affected accounts</p>
</li>
<li><p>Number of affected resources and breakdown of their statuses</p>
</li>
<li><p>Resources affected within each account</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1715870755757/14d67354-42a2-43b5-9d69-f494b1795fc5.png" alt class="image--center mx-auto" /></p>
<p>This tool can export all this information to a CSV file!</p>
<h2 id="heading-prerequisites-for-using-the-tool">Prerequisites for using the tool</h2>
<ul>
<li><p>The organizational view of AWS Health is enabled.</p>
</li>
<li><p>AWS authentication credentials to access AWS Health and AWS Organizations</p>
<ul>
<li>Authentication credentials for the management or delegated administrator accounts are required to use the organizational view.</li>
</ul>
</li>
<li><p>A business plan or higher-level AWS support contract</p>
<ul>
<li>Required to use the AWS Health API</li>
</ul>
</li>
</ul>
<h2 id="heading-how-to-use">How to Use</h2>
<p>Download the latest binary suitable for your environment from the GitHub repository's releases page.</p>
<p><a target="_blank" href="https://github.com/hayao-k/aws-health-exporter/releases">https://github.com/hayao-k/aws-health-exporter/releases</a></p>
<pre><code class="lang-bash">wget https://github.com/hayao-k/aws-health-exporter/releases/download/v0.8.1/aws-health-exporter_0.8.1_linux_amd64.tar.gz
tar xvf aws-health-exporter_0.8.1_linux_amd64.tar.gz
</code></pre>
<p>To use AWS Health Exporter, run the binary with the desired flags. Below are the available flags:</p>
<ul>
<li><p><code>--event-filter</code>, <code>--filter</code>, <code>-f</code>: Filter events by service name, event status, and other criteria.</p>
</li>
<li><p><code>--status-code</code>, <code>-c</code>: Filter entity by status code. Possible values are IMPAIRED, UNIMPAIRED, UNKNOWN, PENDING and RESOLVED</p>
</li>
<li><p><code>--echo</code>, <code>-e</code>: Echo CSV content to standard output.</p>
</li>
<li><p><code>--profile</code>, <code>-p</code>: Specify the AWS credential profile to use.</p>
</li>
<li><p><code>--account-id</code>, <code>-i</code>: Specify a single account ID to process (optional).</p>
</li>
<li><p><code>--output-file</code>, <code>--file-name</code>, <code>o</code>: Specify the output CSV file name.</p>
</li>
</ul>
<h3 id="heading-details-of-the-event-filtering-option">Details of the event filtering option</h3>
<p>The <code>--event-filter</code> option allows you to specify complex filtering criteria. Below is a table of the available fields that can be included in the filter criteria:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Field</td><td>Description</td><td>Possible Values</td></tr>
</thead>
<tbody>
<tr>
<td><code>service</code></td><td>Filter events by AWS service name.</td><td>e.g., <code>LAMBDA</code>, <code>RDS</code>, <code>EKS</code></td></tr>
<tr>
<td><code>status</code></td><td>Filter events by status.</td><td><code>open</code>, <code>closed</code>, <code>upcoming</code></td></tr>
<tr>
<td><code>category</code></td><td>Filter events by category.</td><td><code>issue</code>, <code>accountNotification</code>, <code>scheduledChange</code>, <code>investigation</code></td></tr>
<tr>
<td><code>region</code></td><td>Filter events by region.</td><td>AWS region codes, e.g., <code>us-east-1</code></td></tr>
<tr>
<td><code>startTime</code></td><td>Filter events by start time.</td><td>ISO 8601 date format</td></tr>
<tr>
<td><code>endTime</code></td><td>Filter events by end time.</td><td>ISO 8601 date format</td></tr>
<tr>
<td><code>lastUpdatedTime</code></td><td>Filter events by last updated time.</td><td>ISO 8601 date format</td></tr>
</tbody>
</table>
</div><p>For <code>startTime</code>, <code>endTime,</code> and <code>lastUpdatedTime</code>, you can specify a time range using <code>from</code> and <code>to</code> in ISO 8601 date format. Here is the structure for determining the time range:</p>
<ul>
<li><code>{from:YYYY-MM-DDTHH:MM:SSZ,to:YYYY-MM-DDTHH:MM:SSZ}</code></li>
</ul>
<h3 id="heading-example-commands">Example Commands</h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Describe RDS events with open status and export to CSV</span>
./health-exporter --event-filter service=RDS,status=open

<span class="hljs-comment"># Describe upcoming LAMBDA events and echo the output to STDOUT</span>
./health-exporter --event-filter service=LAMBDA,status=upcoming --<span class="hljs-built_in">echo</span>

<span class="hljs-comment"># Describe only events in the Tokyo region and specify their last updated time.</span>
./health-exporter ----event-filter <span class="hljs-string">"lastUpdatedTime={from=2024-03-01T00:00:00Z,to=2024-05-02T23:59:59Z},region=ap-northeast-1"</span>

<span class="hljs-comment"># Get entities with pending status only and specify a custom file name</span>
./health-exporter --status-code PENDING --output-file my_event_details.csv

<span class="hljs-comment"># Get events using the specified profile</span>
./health-exporter --profile my-profile

<span class="hljs-comment"># Process only a single account</span>
./health-exporter --account-id 123456789012
</code></pre>
<h3 id="heading-execution-example">Execution Example</h3>
<p>When you execute the command, an interactive prompt will be displayed. In the following example, the <code>--event-filter</code> flag extracts only the upcoming status events related to AWS Lambda.</p>
<pre><code class="lang-bash">$ health-exporter --event-filter service=LAMBDA,status=upcoming --status-code PENDING
Use the arrow keys to navigate: ↓ ↑ → ← 
? Select an event: 
  ▸ LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (us-east-1, 2024-10-14 07:00:00)
    LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (ap-northeast-1, 2024-10-14 07:00:00)
    LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (ap-northeast-1, 2024-06-12 07:00:00)
    LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (ap-southeast-2, 2024-10-14 07:00:00)
↓   LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (us-east-1, 2024-06-12 07:00:00)
</code></pre>
<p>From the prompt, select the event you want to output. After selection, the tool will gather related account and entity information and output it to a CSV file.</p>
<pre><code class="lang-bash">✔ LAMBDA - AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT (us-east-1, 2024-10-14 07:00:00)
Event details have been written to AWS_LAMBDA_PLANNED_LIFECYCLE_EVENT_2024-10-14_07-00-00_us-east-1_PENDING.csv.
</code></pre>
<p>The output CSV will contain information such as Account ID, Account Name, Region, Identifier, Status, and Last Updated. In this example, since <code>--status-code PENDING</code> was specified during command execution, only resources with PENDING status are output.</p>
<pre><code class="lang-xml">Account ID, Account Name, Region, Identifier, Status, Last Updated
000000000000,account-0000,us-east-1,arn:aws:lambda:us-east-1:000000000000:function:Old_Runtime_Lambda_Function-1PBKPZPFSJ058,PENDING,2024-04-21 20:11:29
111111111111,account-1111,us-east-1,arn:aws:lambda:us-east-1:111111111111:function:Old_Runtime_Lambda_Function-uuTi2u7DbooD,PENDING,2024-04-21 20:11:29
111111111111,account-1111,us-east-1,arn:aws:lambda:us-east-1:111111111111:function:Old_Runtime_Lambda_Function-omdieC8Umobo,PENDING,2024-04-21 20:11:29
222222222222,account-2222,us-east-1,arn:aws:lambda:us-east-1:222222222222:function:Old_Runtime_Lambda_Function-ULZ27BYSQ0MN,PENDING,2024-04-21 20:11:29
222222222222,account-2222,us-east-1,arn:aws:lambda:us-east-1:222222222222:function:Old_Runtime_Lambda_Function-10YNGBMU46VP9,PENDING,2024-04-21 20:11:29
222222222222,account-2222,us-east-1,arn:aws:lambda:us-east-1:222222222222:function:Old_Runtime_Lambda_Function-CEgHAu41udFy,PENDING,2024-04-21 20:11:29
333333333333,account-3333,us-east-1,arn:aws:lambda:us-east-1:333333333333:function:Old_Runtime_Lambda_Function-zNKRpLWP0pXB,PENDING,2024-04-21 20:11:29
333333333333,account-3333,us-east-1,arn:aws:lambda:us-east-1:333333333333:function:Old_Runtime_Lambda_Function-24ES8MRQJ9R6,PENDING,2024-04-21 20:11:29
444444444444,account-4444,us-east-1,arn:aws:lambda:us-east-1:444444444444:function:Old_Runtime_Lambda_Function-134QIS8IYF84K,PENDING,2024-04-21 20:11:29
444444444444,account-4444,us-east-1,arn:aws:lambda:us-east-1:444444444444:function:Old_Runtime_Lambda_Function-B97VeyrZNXIy,PENDING,2024-04-21 20:11:29
</code></pre>
<h2 id="heading-mechanism"><strong>Mechanism</strong></h2>
<p>Primarily uses 3 AWS Health APIs.</p>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/health/latest/APIReference/API_DescribeEventsForOrganization.html"><strong>DescribeEventsForOrganization API</strong></a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/health/latest/APIReference/API_DescribeAffectedAccountsForOrganization.html"><strong>DescribeAffectedAccountsForOrganization API</strong></a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/health/latest/APIReference/API_DescribeAffectedEntitiesForOrganization.html"><strong>DescribeAffectedEntitiesForOrganization API</strong></a></p>
</li>
</ul>
<h3 id="heading-describeeventsfororganization-api"><strong>DescribeEventsForOrganization API</strong></h3>
<p>Calls the DescribeEventsForOrganization API to retrieve relevant events based on the filter conditions specified on the command line. This API returns only an overview of the events, so information about affected accounts or resources is not included.</p>
<h3 id="heading-describeaffectedaccountsfororganization-api"><strong>DescribeAffectedAccountsForOrganization API</strong></h3>
<p>This API retrieves a list of accounts within the organization affected by the selected event.</p>
<h3 id="heading-describeaffectedentitiesfororganization-api"><strong>DescribeAffectedEntitiesForOrganization API</strong></h3>
<p>This API returns a list of entities affected by one or more events in one or more accounts within the organization.</p>
<p>When the user selects an event through the interactive prompt, information obtained from these APIs is formatted and output as a CSV file.</p>
<p>I hope this helps you.</p>
]]></content:encoded></item><item><title><![CDATA[AI Chatbot powered by Amazon Bedrock 🚀🤖]]></title><description><![CDATA[Introduction
I have created a sample chatbot application that uses Chainlit and LangChain to showcase Amazon Bedrock.
You can interact with the AI assistant while switching between multiple models.

This sample application has been tested in the foll...]]></description><link>https://hayao-k.dev/ai-chatbot-powered-by-amazon-bedrock</link><guid isPermaLink="true">https://hayao-k.dev/ai-chatbot-powered-by-amazon-bedrock</guid><category><![CDATA[AWS]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[Amazon Bedrock]]></category><category><![CDATA[langchain]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Fri, 29 Sep 2023 16:34:09 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I have created a sample chatbot application that uses <a target="_blank" href="https://github.com/Chainlit/chainlit">Chainlit</a> and <a target="_blank" href="https://github.com/langchain-ai/langchain">LangChain</a> to showcase Amazon Bedrock.</p>
<p>You can interact with the AI assistant while switching between multiple models.</p>
<p><img src="https://d7xrug9ye6o3w.cloudfront.net/overview.png" alt /></p>
<p>This sample application has been tested in the following environments:</p>
<ul>
<li><p>AWS Region: us-east-1</p>
</li>
<li><p>AWS Copilot CLI: v1.30.1</p>
</li>
<li><p>Python: v3.11.5</p>
</li>
<li><p>boto3: v1.28.57</p>
</li>
<li><p>LangChain: v0.0.305</p>
</li>
<li><p>Chainlit: v0.7.0</p>
</li>
</ul>
<p><strong>Note:</strong> To support the GA Version of the Bedrock API, make sure to use versions of LangChain and boto3 that are newer than those mentioned above.</p>
<h2 id="heading-source-code">Source Code</h2>
<p>Check out my GitHub repository!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/Bedrock-AIChatbot-Sample">https://github.com/hayao-k/Bedrock-AIChatbot-Sample</a></div>
<p> </p>
<h2 id="heading-start-using-bedrock">Start using Bedrock</h2>
<p>Before you can start using Amazon Bedrock, you must request access to each model. To add access to a model, go to the Model Access section in the Bedrock console and click Edit.</p>
<p><img src="https://camo.qiitausercontent.com/52dab616000be207e88689463d49041929f2cf9e/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f32343832323766382d353636332d383330642d653938342d3437656237303835306438612e706e67" alt /></p>
<p>Select the models you want to use and save the settings.</p>
<p><strong>Note:</strong> Third-party models such as Jurassic-2 and Claude require access through the AWS Marketplace. This means you will also be charged a Marketplace fee for using the model.</p>
<p><img src="https://camo.qiitausercontent.com/9318b9aede91ab7bfd323100df7609eaa6695aee/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f62613838663532332d623737372d346664352d616435372d3231323035306133643738352e706e67" alt /></p>
<p>It will take some time for each model to become available; models with an Access status of Access granted are ready for use.</p>
<p><strong>Note:</strong> Some models, such as Titan Text G1 - Express, are in Preview and may not be immediately available.</p>
<p><img src="https://camo.qiitausercontent.com/bb97a8907a589946cf1917e3c9fd3cb6823598b5/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f36373839356437642d653835382d623035382d346539382d3063613535383236613532352e706e67" alt /></p>
<h2 id="heading-how-to-deploy">How to Deploy</h2>
<p>The GitHub repository for the sample application contains a manifest file for deploying the container to AWS App Runner using the AWS Copilot CLI.</p>
<p>Follow these steps to deploy:</p>
<ol>
<li><p>Install the AWS Copilot CLI if necessary</p>
<pre><code class="lang-bash"> sudo curl -Lo /usr/<span class="hljs-built_in">local</span>/bin/copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux &amp;&amp; sudo chmod +x /usr/<span class="hljs-built_in">local</span>/bin/copilot
</code></pre>
</li>
<li><p>Deploy to App Runner!</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">export</span> AWS_REGION=us-east-1
 copilot app init bedrockchat-app
 copilot deploy --name bedrockchat --env dev
</code></pre>
</li>
</ol>
<p>If the deployment is successful, access the URL displayed in the message. If the following screen appears, the deployment has succeeded!</p>
<p><img src="https://camo.qiitausercontent.com/24d4fa1ce28ce17c4730ec90e452ebfcb1e51c07/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f64633066306631362d613865622d366563392d356164322d3937326436633732333937332e706e67" alt /></p>
<p>To delete an environment, execute the following command:</p>
<pre><code class="lang-bash">copilot app delete
</code></pre>
<h2 id="heading-how-to-use-the-app">How to use the app</h2>
<p>The model, temperature, and maximum token size can be changed from the Settings panel.</p>
<p><img src="https://camo.qiitausercontent.com/31b9b0ff3338c0f86c92781e61af24b127b7c8bf/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f39396463326339352d346136622d396431652d646166392d3731663939393136323066372e706e67" alt /></p>
<p>The sample code allows you to choose between Claude v2, Jurassic-2 Ultra, and Amazon Titan Text G1 - Express. This enables you to compare performance while switching between models.</p>
<p><strong>Note:</strong> As of 9/29/2023, Amazon Titan Text G1 - Express is in Preview status. It will be rolled out gradually, so it may not be available for some accounts.</p>
<p><img src="https://camo.qiitausercontent.com/f8034393a99c63c88655bd8adf2381ee4d82e42e/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f64643335643366662d643561302d666265632d623335392d6430383564376364616137372e706e67" alt /></p>
<p>You can enjoy a free-flowing conversation with the AI assistant! The conversation history during the session is retained, allowing it to reply to you while considering the context.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695960398620/5ae3791e-e861-486d-9ba6-163ce531fe24.png" alt class="image--center mx-auto" /></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Running Amazon S3 Mountpoint Inside a Container]]></title><description><![CDATA[Introduction
Here is a simple example of running Mountpoint for Amazon S3 from inside a container

Created with information as of 3/21/2023 (version: 0.2.0-b8363a4)

Mountpoint for Amazon S3 is currently in alpha release and should not be used in pro...]]></description><link>https://hayao-k.dev/running-amazon-s3-mountpoint-inside-a-container</link><guid isPermaLink="true">https://hayao-k.dev/running-amazon-s3-mountpoint-inside-a-container</guid><category><![CDATA[AWS]]></category><category><![CDATA[Docker]]></category><category><![CDATA[S3]]></category><category><![CDATA[mountpoint-s3]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Wed, 22 Mar 2023 12:26:08 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Here is a simple example of running Mountpoint for Amazon S3 from inside a container</p>
<ul>
<li><p>Created with information as of 3/21/2023 (version: 0.2.0-b8363a4)</p>
</li>
<li><p>Mountpoint for Amazon S3 is currently in alpha release and should not be used in production workloads</p>
</li>
</ul>
<h2 id="heading-dockerfile">Dockerfile</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/container-mountpoint-s3">https://github.com/hayao-k/container-mountpoint-s3</a></div>
<p> </p>
<pre><code class="lang-plaintext">FROM rust:1.68.0 as Build

RUN apt-get update &amp;&amp; apt-get install -y \
    clang\
    cmake \
    curl \
    fuse \
    git \
    libfuse-dev \
    pkg-config \
 &amp;&amp; apt-get clean \
 &amp;&amp; rm -rf /var/lib/apt/lists/* \
 &amp;&amp; git clone --recurse-submodules https://github.com/awslabs/mountpoint-s3.git \
 &amp;&amp; cd mountpoint-s3 \
 &amp;&amp; cargo build --release


FROM debian:bullseye-slim
RUN apt-get update &amp;&amp; apt-get install -y \
    ca-certificates \
    libfuse-dev \
    sudo \
 &amp;&amp; apt-get clean \
 &amp;&amp; rm -rf /var/lib/apt/lists/*

COPY --from=build /mountpoint-s3/target/release/mount-s3 /usr/local/bin/mount-s3

RUN chmod 777 /usr/local/bin/mount-s3

RUN useradd -ms /bin/bash mount-s3-user \
 &amp;&amp; echo '%sudo ALL=(ALL) NOPASSWD:ALL' &gt;&gt; /etc/sudoers \
 &amp;&amp; adduser mount-s3-user sudo

USER mount-s3-user
</code></pre>
<h2 id="heading-getting-started">Getting started</h2>
<p>Image Build</p>
<pre><code class="lang-bash">docker image build -t mount-s3:latest .
</code></pre>
<p>Run</p>
<pre><code class="lang-bash">docker container run --privileged --rm -it mount-s3:latest bash
</code></pre>
<p>Enjoy</p>
<pre><code class="lang-bash">mount-s3-user@ce43831fda04:~$ sudo mount-s3 &lt;bucket_name&gt; /mnt --allow-other --region ap-northeast-1
mount-s3-user@ce43831fda04:~$ ls -l /mnt/test.json
-rw-r--r-- 1 root root 306424 Feb 21 02:42 /mnt/test.json
</code></pre>
<h2 id="heading-ec2-on-docker-consideration">EC2 on Docker Consideration</h2>
<p>If using EC2 IAM roles for AWS credentials, increasing the IMDSv2 hop limit from 1 to 2 in the instance metadata options is <a target="_blank" href="https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#imds-considerations">recommended</a>.</p>
<blockquote>
<p>In a container environment, if the hop limit is 1, the IMDSv2 response does not return because going to the container is considered an additional network hop. To avoid the process of falling back to IMDSv1 and the resultant delay, in a container environment we recommend that you set the hop limit to 2</p>
</blockquote>
<pre><code class="lang-bash">aws ec2 modify-instance-metadata-options \
    --instance-id i-xxxxxxxxxxxxxxxxx \
    --http-put-response-hop-limit 2 \
    --http-endpoint enabled
</code></pre>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Creating AWS resources for GitHub audit log streaming with CloudFormation.]]></title><description><![CDATA[Introduction
GitHub Enterprise Cloud audit logs support log streaming to various cloud providers.
https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-a...]]></description><link>https://hayao-k.dev/creating-aws-resources-for-github-audit-log-streaming-with-cloudformation</link><guid isPermaLink="true">https://hayao-k.dev/creating-aws-resources-for-github-audit-log-streaming-with-cloudformation</guid><category><![CDATA[AWS]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[audit]]></category><category><![CDATA[cloudformation]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Fri, 17 Mar 2023 12:53:10 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>GitHub Enterprise Cloud audit logs support log streaming to various cloud providers.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise">https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise</a></div>
<p> </p>
<p>Streaming audit logs to Amazon S3 can be done via OpenID Connect. This requires the creation of an OIDC Provider and IAM role on the AWS side and an S3 bucket for log storage.</p>
<p>Please refer to the above document for detailed instructions.</p>
<p>It is not something that is created many times, but I have created a CloudFormation template and hope it will be helpful for those who want to create a new one quickly and easily.</p>
<h2 id="heading-resources-to-be-created">Resources to be created</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Logical ID</td><td>Type</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>AuditLogBucket</td><td>AWS::S3::Bucket</td><td>S3 bucket for audit log</td></tr>
<tr>
<td>AuditLogbucketPolicy</td><td>AWS::S3::BucketPolicy</td><td>S3 bucket policy for audit log</td></tr>
<tr>
<td>AccessLogBucket</td><td>AWS::S3::Bucket</td><td>S3 bucket for server access log</td></tr>
<tr>
<td>AccessLogBucketPolicy</td><td>AWS::S3::BucketPolicy</td><td>S3 bucket policy for server access log</td></tr>
<tr>
<td>AuditLogEncryptionKey</td><td>AWS::KMS::Key</td><td>Customer Managed Key for audit log encryption</td></tr>
<tr>
<td>AuditLogEncryptionKeyAlias</td><td>AWS::KMS::Alias</td><td>Customer Managed Key alias</td></tr>
<tr>
<td>AuditLogStremingPolicy</td><td>AWS::IAM::ManagedPolicy</td><td>IAM policy for audit log streaming</td></tr>
<tr>
<td>AuditLogStreamingRole</td><td>AWS::IAM::Role</td><td>IAM Role for audit log streaming</td></tr>
<tr>
<td>GithubAuditLogOIDCProvider</td><td>AWS::IAM::OIDCProvider</td><td>GitHub OIDC provider</td></tr>
</tbody>
</table>
</div><h2 id="heading-cloudformation-template">CloudFormation template</h2>
<p>Github repository is here</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/CFn-github-audit-log-streaming">https://github.com/hayao-k/CFn-github-audit-log-streaming</a></div>
<p> </p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">"2010-09-09"</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">Create</span> <span class="hljs-string">AWS</span> <span class="hljs-string">resources</span> <span class="hljs-string">required</span> <span class="hljs-string">for</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Enterprise</span> <span class="hljs-string">Cloud</span> <span class="hljs-string">audit</span> <span class="hljs-string">log</span> <span class="hljs-string">streaming</span>

<span class="hljs-attr">Parameters:</span>
  <span class="hljs-attr">RetainDays:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Number</span> <span class="hljs-string">of</span> <span class="hljs-string">days</span> <span class="hljs-string">to</span> <span class="hljs-string">keep</span> <span class="hljs-string">audit</span> <span class="hljs-string">log.</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>
    <span class="hljs-attr">Default:</span> <span class="hljs-number">365</span>
  <span class="hljs-attr">EnterpriseName:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Your</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Enterprise</span> <span class="hljs-string">Name.</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>
    <span class="hljs-attr">Default:</span> <span class="hljs-string">EXAPMLECORP</span>
  <span class="hljs-attr">AccessLogPrefix:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Server</span> <span class="hljs-string">access</span> <span class="hljs-string">log-prefix</span> <span class="hljs-string">of</span> <span class="hljs-string">audit</span> <span class="hljs-string">log</span> <span class="hljs-string">bucket.</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>
    <span class="hljs-attr">Default:</span> <span class="hljs-string">logs/</span>

<span class="hljs-attr">Metadata:</span> 
  <span class="hljs-attr">AWS::CloudFormation::Interface:</span> 
    <span class="hljs-attr">ParameterGroups:</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Label:</span> 
          <span class="hljs-attr">default:</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Configuration</span>
        <span class="hljs-attr">Parameters:</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-string">EnterpriseName</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">Label:</span> 
          <span class="hljs-attr">default:</span> <span class="hljs-string">Audit</span> <span class="hljs-string">Log</span> <span class="hljs-string">Configuration</span>
        <span class="hljs-attr">Parameters:</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-string">RetainDays</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">AccessLogPrefix</span>

<span class="hljs-attr">Resources:</span>
  <span class="hljs-comment"># Customer Managed Key for audit log encryption</span>
  <span class="hljs-attr">AuditLogEncryptionKey:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::KMS::Key</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">Description:</span> <span class="hljs-string">Customer</span> <span class="hljs-string">Managed</span> <span class="hljs-string">Key</span> <span class="hljs-string">for</span> <span class="hljs-string">audit</span> <span class="hljs-string">log</span> <span class="hljs-string">encryption</span>
      <span class="hljs-attr">Enabled:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">EnableKeyRotation:</span> <span class="hljs-literal">True</span>
      <span class="hljs-attr">KeyPolicy:</span>
        <span class="hljs-attr">Version:</span> <span class="hljs-string">'2012-10-17'</span>
        <span class="hljs-attr">Id:</span> <span class="hljs-string">key-default-1</span>
        <span class="hljs-attr">Statement:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Sid:</span> <span class="hljs-string">Allow</span> <span class="hljs-string">administration</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">key</span>
          <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
          <span class="hljs-attr">Principal:</span>
            <span class="hljs-attr">AWS:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">arn:aws:iam::${AWS::AccountId}:root</span>
          <span class="hljs-attr">Action:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Create*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Describe*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Enable*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:List*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Put*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Update*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Revoke*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Disable*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Get*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Delete*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:ScheduleKeyDeletion</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:CancelKeyDeletion</span>
          <span class="hljs-attr">Resource:</span> <span class="hljs-string">'*'</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Sid:</span> <span class="hljs-string">Allow</span> <span class="hljs-string">local</span> <span class="hljs-string">use</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">key</span>
          <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
          <span class="hljs-attr">Principal:</span>
            <span class="hljs-attr">AWS:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">arn:aws:iam::${AWS::AccountId}:root</span>
          <span class="hljs-attr">Action:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Encrypt</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:Decrypt</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:ReEncrypt*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:GenerateDataKey*</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">kms:DescribeKey</span>
          <span class="hljs-attr">Resource:</span> <span class="hljs-string">'*'</span>

  <span class="hljs-comment"># Customer Managed Key ALias</span>
  <span class="hljs-attr">AuditLogEncryptionKeyAlias:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::KMS::Alias</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">AliasName:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">alias/${AWS::StackName}-key</span>
      <span class="hljs-attr">TargetKeyId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AuditLogEncryptionKey</span>

  <span class="hljs-comment"># S3 bucket for audit log</span>
  <span class="hljs-attr">AuditLogBucket:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::Bucket</span>
    <span class="hljs-attr">DeletionPolicy:</span> <span class="hljs-string">Retain</span>
    <span class="hljs-attr">UpdateReplacePolicy:</span> <span class="hljs-string">Retain</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">BucketEncryption:</span>
        <span class="hljs-attr">ServerSideEncryptionConfiguration:</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ServerSideEncryptionByDefault:</span> 
              <span class="hljs-attr">SSEAlgorithm:</span> <span class="hljs-string">aws:kms</span>
              <span class="hljs-attr">KMSMasterKeyID:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AuditLogEncryptionKey.Arn</span>
            <span class="hljs-attr">BucketKeyEnabled:</span> <span class="hljs-literal">true</span>    
      <span class="hljs-attr">LifecycleConfiguration:</span>
        <span class="hljs-attr">Rules:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Id:</span> <span class="hljs-string">Retain</span>
            <span class="hljs-attr">Status:</span> <span class="hljs-string">Enabled</span>
            <span class="hljs-attr">ExpirationInDays:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">RetainDays</span>
      <span class="hljs-attr">PublicAccessBlockConfiguration:</span> 
        <span class="hljs-attr">BlockPublicAcls:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">BlockPublicPolicy:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">IgnorePublicAcls:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">RestrictPublicBuckets:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">LoggingConfiguration:</span>
        <span class="hljs-attr">DestinationBucketName:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AccessLogBucket</span>
        <span class="hljs-attr">LogFilePrefix:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AccessLogPrefix</span>
      <span class="hljs-attr">VersioningConfiguration:</span>
        <span class="hljs-attr">Status:</span> <span class="hljs-string">Enabled</span>
      <span class="hljs-attr">OwnershipControls:</span>
        <span class="hljs-attr">Rules:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ObjectOwnership:</span> <span class="hljs-string">BucketOwnerEnforced</span>

  <span class="hljs-comment"># S3 bucket policy for audit log</span>
  <span class="hljs-attr">AudtiLogBucketPolicy:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::BucketPolicy</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Bucket:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AuditLogBucket</span>
      <span class="hljs-attr">PolicyDocument:</span>
        <span class="hljs-attr">Statement:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Effect:</span> <span class="hljs-string">Deny</span>
          <span class="hljs-attr">Principal:</span> <span class="hljs-string">'*'</span>
          <span class="hljs-attr">Action:</span> <span class="hljs-string">'s3:*'</span>
          <span class="hljs-attr">Resource:</span> 
            <span class="hljs-bullet">-</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AuditLogBucket.Arn}/*</span>
            <span class="hljs-bullet">-</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AuditLogBucket.Arn</span>
          <span class="hljs-attr">Condition:</span>
            <span class="hljs-attr">Bool:</span> 
              <span class="hljs-attr">aws:SecureTransport:</span> <span class="hljs-literal">false</span>

  <span class="hljs-comment"># S3 bucket for server access log</span>
  <span class="hljs-attr">AccessLogBucket:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::Bucket</span>
    <span class="hljs-attr">DeletionPolicy:</span> <span class="hljs-string">Retain</span>
    <span class="hljs-attr">UpdateReplacePolicy:</span> <span class="hljs-string">Retain</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">BucketEncryption:</span>
        <span class="hljs-attr">ServerSideEncryptionConfiguration:</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ServerSideEncryptionByDefault:</span> 
              <span class="hljs-attr">SSEAlgorithm:</span> <span class="hljs-string">AES256</span>
      <span class="hljs-attr">LifecycleConfiguration:</span>
        <span class="hljs-attr">Rules:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Id:</span> <span class="hljs-string">Retain</span>
            <span class="hljs-attr">Status:</span> <span class="hljs-string">Enabled</span>
            <span class="hljs-attr">ExpirationInDays:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">RetainDays</span>
      <span class="hljs-attr">PublicAccessBlockConfiguration:</span> 
        <span class="hljs-attr">BlockPublicAcls:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">BlockPublicPolicy:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">IgnorePublicAcls:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">RestrictPublicBuckets:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">VersioningConfiguration:</span>
        <span class="hljs-attr">Status:</span> <span class="hljs-string">Enabled</span>
      <span class="hljs-attr">OwnershipControls:</span>
        <span class="hljs-attr">Rules:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ObjectOwnership:</span> <span class="hljs-string">BucketOwnerEnforced</span>

  <span class="hljs-comment"># S3 bucket policy for server access log</span>
  <span class="hljs-attr">AccessLogBucketPolicy:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::BucketPolicy</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Bucket:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AccessLogBucket</span>
      <span class="hljs-attr">PolicyDocument:</span>
        <span class="hljs-attr">Statement:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Effect:</span> <span class="hljs-string">Deny</span>
            <span class="hljs-attr">Principal:</span> <span class="hljs-string">'*'</span>
            <span class="hljs-attr">Action:</span> <span class="hljs-string">'s3:*'</span>
            <span class="hljs-attr">Resource:</span> 
              <span class="hljs-bullet">-</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AccessLogBucket.Arn}/*</span>
              <span class="hljs-bullet">-</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AccessLogBucket.Arn</span>
            <span class="hljs-attr">Condition:</span>
              <span class="hljs-attr">Bool:</span> 
                <span class="hljs-attr">aws:SecureTransport:</span> <span class="hljs-literal">false</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
            <span class="hljs-attr">Principal:</span>
              <span class="hljs-attr">Service:</span> <span class="hljs-string">logging.s3.amazonaws.com</span>
            <span class="hljs-attr">Action:</span> <span class="hljs-string">s3:PutObject</span>
            <span class="hljs-attr">Resource:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AccessLogBucket.Arn}/${AccessLogPrefix}*</span>
            <span class="hljs-attr">Condition:</span>
              <span class="hljs-attr">ArnLike:</span>
                <span class="hljs-attr">aws:SourceArn:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AuditLogBucket.Arn</span>
              <span class="hljs-attr">StringEquals:</span> 
                <span class="hljs-attr">aws:SourceAccount:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AWS::AccountId</span>

  <span class="hljs-comment"># IAM policy for audit log streaming role</span>
  <span class="hljs-attr">AuditLogStremingPolicy:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::IAM::ManagedPolicy</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Description:</span> <span class="hljs-string">Policy</span> <span class="hljs-string">for</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Audit</span> <span class="hljs-string">Log</span> <span class="hljs-string">Streaming</span>
      <span class="hljs-attr">PolicyDocument:</span>
        <span class="hljs-attr">Version:</span> <span class="hljs-number">2012-10-17</span>
        <span class="hljs-attr">Statement:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
            <span class="hljs-attr">Action:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">s3:PutObject</span>
            <span class="hljs-attr">Resource:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AuditLogBucket.Arn}/*</span>

  <span class="hljs-comment"># IAM Role for audit log streaming role</span>
  <span class="hljs-attr">AuditLogStreamingRole:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::IAM::Role</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">ManagedPolicyArns:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AuditLogStremingPolicy</span>
      <span class="hljs-attr">AssumeRolePolicyDocument:</span>
        <span class="hljs-attr">Statement:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
            <span class="hljs-attr">Action:</span> <span class="hljs-string">sts:AssumeRoleWithWebIdentity</span>
            <span class="hljs-attr">Principal:</span>
              <span class="hljs-attr">Federated:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">GithubAuditLogOIDCProvider</span>
            <span class="hljs-attr">Condition:</span>
              <span class="hljs-attr">StringEquals:</span>
                <span class="hljs-attr">oidc-configuration.audit-log.githubusercontent.com:sub:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">https://github.com/${EnterpriseName}</span>
                <span class="hljs-attr">oidc-configuration.audit-log.githubusercontent.com:aud:</span> <span class="hljs-string">sts.amazonaws.com</span>

  <span class="hljs-attr">GithubAuditLogOIDCProvider:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::IAM::OIDCProvider</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Url:</span> <span class="hljs-string">https://oidc-configuration.audit-log.githubusercontent.com</span>
      <span class="hljs-attr">ClientIdList:</span> 
        <span class="hljs-bullet">-</span> <span class="hljs-string">sts.amazonaws.com</span>
      <span class="hljs-attr">ThumbprintList:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">7e6db7b7584d8cf2003e0931e6cfc41a3a62d3df</span>

<span class="hljs-attr">Outputs:</span>
  <span class="hljs-attr">IAMRole:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">IAM</span> <span class="hljs-string">role</span> <span class="hljs-string">for</span> <span class="hljs-string">audit</span> <span class="hljs-string">log</span> <span class="hljs-string">streaming</span> <span class="hljs-string">role</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AuditLogStreamingRole.Arn</span>
  <span class="hljs-attr">AuditLogBucket:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">S3</span> <span class="hljs-string">bucket</span> <span class="hljs-string">for</span> <span class="hljs-string">audit</span> <span class="hljs-string">log</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AuditLogBucket</span>
</code></pre>
<h2 id="heading-deploy">Deploy</h2>
<p>3 parameters are required.</p>
<ul>
<li><p>GitHub Enterprise Name (<code>EnterpriseName</code>)</p>
</li>
<li><p>Number of days to keep audit log (<code>RetainDays</code>)</p>
</li>
<li><p>Server access log-prefix of audit log bucket (<code>AccessLogPrefix</code>)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679057888686/8ca383f1-ab25-465f-aaca-63bb3041cdd1.png" alt class="image--center mx-auto" /></p>
<p>After deploying the stack, set the output S3 bucket name and IAM Role ARN to GitHub Enterprise Cloud.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679057919688/831b594f-a3a6-4fd1-9ba3-a5acac0d9e6e.png" alt class="image--center mx-auto" /></p>
<p>If the endpoint connection check is successful, save the configuration.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679057949987/2d2f0c38-2150-44b4-ae7a-d8c3b384b7cc.png" alt class="image--center mx-auto" /></p>
<p>You can see the audit log in the AuditLogBucket!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1679057968048/66a1fc43-fbbd-4534-a7dd-9d0ad56bf330.png" alt class="image--center mx-auto" /></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Automate AWS Enterprise Support Activation for Member Accounts]]></title><description><![CDATA[Why do we need automation?
Member accounts added to AWS Organizations after subscribing to Enterprise Support are not enrolled in Enterprise Support.
To register a new member account with Enterprise Support, you must open a support case in the manage...]]></description><link>https://hayao-k.dev/automate-aws-enterprise-support-activation-for-member-accounts</link><guid isPermaLink="true">https://hayao-k.dev/automate-aws-enterprise-support-activation-for-member-accounts</guid><category><![CDATA[AWS]]></category><category><![CDATA[stepfunction]]></category><category><![CDATA[organization]]></category><category><![CDATA[control tower]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Mon, 13 Mar 2023 07:10:29 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-why-do-we-need-automation">Why do we need automation?</h2>
<p>Member accounts added to AWS Organizations after subscribing to Enterprise Support are not enrolled in Enterprise Support.</p>
<p>To register a new member account with Enterprise Support, you must open a support case in the management account.</p>
<h2 id="heading-example">Example</h2>
<p>This example is a simple workflow that executes case creation, close confirmation, and notification with AWS Step Functions.</p>
<h3 id="heading-input">Input</h3>
<p>You can use Amazon EventBrige to trigger the CreateAccount event in AWS Organizations or the CreateManagedAccount event in AWS Control Tower to launch the state machine.</p>
<p>Therefore, the input for the state machine is a single account as follows.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"AccountId"</span>: <span class="hljs-string">"&lt;Account ID&gt;"</span>
}
</code></pre>
<h3 id="heading-state-machine-definition">State machine definition</h3>
<p>The process flow is as follows. Creating cases and checking status uses AWS SDK service integrations.</p>
<ul>
<li><p>In the CeateCase state, create a support case with the account ID received from the state machine startup input as the activation target</p>
</li>
<li><p>Execute the DescribeCases state based on the Case ID returned as the result of the CreateCase state task.</p>
<ul>
<li><p>DescribeCases API requires passing a list of case IDs, so use the built-in function <a target="_blank" href="https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-arrays">States.Array</a>.</p>
</li>
<li><p>Since the task result is also a list, specify <code>$.Cases[0]</code> in OutputPath.</p>
</li>
</ul>
</li>
<li><p>In the Choice state, check the status of support cases from the DescirbeCases output</p>
<ul>
<li><p>If <code>resolved</code>, proceed to SNS Pulibsh state.</p>
</li>
<li><p>Otherwise, wait for a specified time in the Wait state and execute DescribeCases again.</p>
</li>
</ul>
</li>
<li><p>In the SNS Publish state, publish a message to the specified SNS Topic</p>
</li>
</ul>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Comment"</span>: <span class="hljs-string">"A description of your state machine"</span>,
  <span class="hljs-attr">"StartAt"</span>: <span class="hljs-string">"CreateCase"</span>,
  <span class="hljs-attr">"States"</span>: {
    <span class="hljs-attr">"CreateCase"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Parameters"</span>: {
        <span class="hljs-attr">"Subject"</span>: <span class="hljs-string">"Enterprise Activation Request for Linked account"</span>,
        <span class="hljs-attr">"ServiceCode"</span>: <span class="hljs-string">"customer-account"</span>,
        <span class="hljs-attr">"SeverityCode"</span>: <span class="hljs-string">"low"</span>,
        <span class="hljs-attr">"CategoryCode"</span>: <span class="hljs-string">"other-account-issues"</span>,
        <span class="hljs-attr">"CommunicationBody.$"</span>: <span class="hljs-string">"States.Format('Please enable Enterprise support for following account ID:\n{}\n', $.AccountId)"</span>,
        <span class="hljs-attr">"Language"</span>: <span class="hljs-string">"en"</span>,
        <span class="hljs-attr">"IssueType"</span>: <span class="hljs-string">"customer-service"</span>
      },
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:states:::aws-sdk:support:createCase"</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"DescribeCases"</span>
    },
    <span class="hljs-attr">"DescribeCases"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Parameters"</span>: {
        <span class="hljs-attr">"CaseIdList.$"</span>: <span class="hljs-string">"States.Array($.CaseId)"</span>,
        <span class="hljs-attr">"IncludeResolvedCases"</span>: <span class="hljs-literal">true</span>
      },
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:states:::aws-sdk:support:describeCases"</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Choice"</span>,
      <span class="hljs-attr">"OutputPath"</span>: <span class="hljs-string">"$.Cases[0]"</span>
    },
    <span class="hljs-attr">"Choice"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Choice"</span>,
      <span class="hljs-attr">"Choices"</span>: [
        {
          <span class="hljs-attr">"Variable"</span>: <span class="hljs-string">"$.Status"</span>,
          <span class="hljs-attr">"StringEquals"</span>: <span class="hljs-string">"resolved"</span>,
          <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"SNS Publish"</span>
        }
      ],
      <span class="hljs-attr">"Default"</span>: <span class="hljs-string">"Wait"</span>
    },
    <span class="hljs-attr">"SNS Publish"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:states:::sns:publish"</span>,
      <span class="hljs-attr">"Parameters"</span>: {
        <span class="hljs-attr">"Message.$"</span>: <span class="hljs-string">"$"</span>,
        <span class="hljs-attr">"TopicArn"</span>: <span class="hljs-string">"arn:aws:sns:us-east-1:123456789012:your-sns-topic"</span>
      },
      <span class="hljs-attr">"End"</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-attr">"Wait"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Wait"</span>,
      <span class="hljs-attr">"Seconds"</span>: <span class="hljs-number">30</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"DescribeCases"</span>
    }
  }
}
</code></pre>
<p>On Workflow Studio, it is displayed as follows.</p>
<p><img src="https://camo.qiitausercontent.com/14a65f611a5cf64ef97a30f752382f112b67b5c0/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3132353130352f32363731343233662d656637332d353038652d346162622d6538383866646663643639662e706e67" alt /></p>
<h3 id="heading-execution-example">Execution example</h3>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/8dce1ad2-49f7-eb50-60ea-24754d03ef39.png" alt /></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[How to restore EC2 instance with multiple ENIs attached from AWS Backup]]></title><description><![CDATA[Goal of this post

Backup EC2 instances with multiple ENIs attached with AWS Backup

Restore EC2 instances with multiple ENIs attached, when restored from a recovery point


How?

Run the StartRestoreJob API, e.g., from the AWS CLI or SDK

Restore jo...]]></description><link>https://hayao-k.dev/how-to-restore-ec2-instance-with-multiple-enis-attached-from-aws-backup</link><guid isPermaLink="true">https://hayao-k.dev/how-to-restore-ec2-instance-with-multiple-enis-attached-from-aws-backup</guid><category><![CDATA[AWS]]></category><category><![CDATA[ec2]]></category><category><![CDATA[Backup]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Thu, 12 Jan 2023 02:56:33 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-goal-of-this-post">Goal of this post</h2>
<ul>
<li><p>Backup EC2 instances with multiple ENIs attached with AWS Backup</p>
</li>
<li><p>Restore EC2 instances with multiple ENIs attached, when restored from a recovery point</p>
</li>
</ul>
<h2 id="heading-how">How?</h2>
<ul>
<li><p>Run the StartRestoreJob API, e.g., from the AWS CLI or SDK</p>
</li>
<li><p>Restore jobs launched from the console cannot customize the network i\nterface</p>
</li>
</ul>
<h2 id="heading-ec2-instance-backup-with-multiple-enis-attached">EC2 instance backup with multiple ENIs attached</h2>
<p>EC2 instances with multiple ENIs attached can also be backed up with AWS Backup. Backup data is stored as AMI, but AMI does not contain network interface information.</p>
<p>However, the metadata of the recovery point includes the network interface information. Recovery point metadata can be checked with the <a target="_blank" href="https://docs.aws.amazon.com/ja_jp/aws-backup/latest/devguide/API_GetRecoveryPointRestoreMetadata.html">GetRecoveryPointRestoreMetadata API</a>.</p>
<p>The following is an example of execution with the AWS CLI.</p>
<pre><code class="lang-bash">$ aws backup get-recovery-point-restore-metadata --backup-vault-name Default --recovery-point-arn arn:aws:ec2:us-west-2::image/ami-xxxxxxxxxxxxxxxxx
{
    <span class="hljs-string">"BackupVaultArn"</span>: <span class="hljs-string">"arn:aws:backup:us-west-2:123456789012:backup-vault:Default"</span>,
    <span class="hljs-string">"RecoveryPointArn"</span>: <span class="hljs-string">"arn:aws:ec2:us-west-2::image/ami-xxxxxxxxxxxxxxxxx"</span>,
    <span class="hljs-string">"RestoreMetadata"</span>: {
        <span class="hljs-string">"CapacityReservationSpecification"</span>: <span class="hljs-string">"{\"CapacityReservationPreference\":\"open\"}"</span>,
        <span class="hljs-string">"CpuOptions"</span>: <span class="hljs-string">"{\"CoreCount\":2,\"ThreadsPerCore\":1}"</span>,
        <span class="hljs-string">"CreditSpecification"</span>: <span class="hljs-string">"{\"CpuCredits\":\"unlimited\"}"</span>,
        <span class="hljs-string">"DisableApiTermination"</span>: <span class="hljs-string">"false"</span>,
        <span class="hljs-string">"EbsOptimized"</span>: <span class="hljs-string">"true"</span>,
        <span class="hljs-string">"HibernationOptions"</span>: <span class="hljs-string">"{\"Configured\":false}"</span>,
        <span class="hljs-string">"InstanceInitiatedShutdownBehavior"</span>: <span class="hljs-string">"stop"</span>,
        <span class="hljs-string">"InstanceType"</span>: <span class="hljs-string">"t4g.micro"</span>,
        <span class="hljs-string">"Monitoring"</span>: <span class="hljs-string">"{\"State\":\"disabled\"}"</span>,
        <span class="hljs-string">"NetworkInterfaces"</span>: <span class="hljs-string">"[{\"AssociatePublicIpAddress\":true,\"DeleteOnTermination\":true,\"Description\":\"\",\"DeviceIndex\":0,\"Groups\":[\"sg-xxxxxxxxxxxxxxxxx\"],\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"NetworkInterfaceId\":\"eni-aaaaaaaaaaaaaaaaa\",\"PrivateIpAddress\":\"172.31.62.169\",\"PrivateIpAddresses\":[{\"Primary\":true,\"PrivateIpAddress\":\"172.31.62.169\"}],\"SecondaryPrivateIpAddressCount\":0,\"SubnetId\":\"subnet-xxxxxxxxxxxxxxxxx\",\"InterfaceType\":\"interface\",\"Ipv4Prefixes\":[],\"Ipv6Prefixes\":[]},{\"AssociatePublicIpAddress\":true,\"DeleteOnTermination\":false,\"Description\":\"\",\"DeviceIndex\":1,\"Groups\":[\"sg-xxxxxxxxxxxxxxxxx\"],\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"NetworkInterfaceId\":\"eni-bbbbbbbbbbbbbbbbb\",\"PrivateIpAddress\":\"172.31.54.130\",\"PrivateIpAddresses\":[{\"Primary\":true,\"PrivateIpAddress\":\"172.31.54.130\"}],\"SecondaryPrivateIpAddressCount\":0,\"SubnetId\":\"subnet-xxxxxxxxxxxxxxxxx\",\"InterfaceType\":\"interface\",\"Ipv4Prefixes\":[],\"Ipv6Prefixes\":[]}]"</span>,
        <span class="hljs-string">"Placement"</span>: <span class="hljs-string">"{\"AvailabilityZone\":\"us-west-2d\",\"GroupName\":\"\",\"Tenancy\":\"default\"}"</span>,
        <span class="hljs-string">"RequireIMDSv2"</span>: <span class="hljs-string">"false"</span>,
        <span class="hljs-string">"SecurityGroupIds"</span>: <span class="hljs-string">"[\"sg-xxxxxxxxxxxxxxxxx\"]"</span>,
        <span class="hljs-string">"SubnetId"</span>: <span class="hljs-string">"subnet-xxxxxxxxxxxxxxxxx"</span>,
        <span class="hljs-string">"VpcId"</span>: <span class="hljs-string">"vpc-xxxxxxxxxxxxxxxxx"</span>,
        <span class="hljs-string">"aws:backup:request-id"</span>: <span class="hljs-string">"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"</span>
    }
}
</code></pre>
<p>In the above, you can see that <code>eni-aaaaaaaaaaaaaaaaa</code> and <code>eni-bbbbbbbbbbbbbbbbb</code> information is included.</p>
<h2 id="heading-how-to-restore-from-a-recovery-point">How to restore from a recovery point</h2>
<p>When launching a restore job in the AWS Backup console, it is not possible to restore an EC2 instance with multiple ENIs attached. This is because the console limits the customizable parameters to the following.</p>
<p><a target="_blank" href="https://docs.aws.amazon.com/aws-backup/latest/devguide/restoring-ec2.html">https://docs.aws.amazon.com/aws-backup/latest/devguide/restoring-ec2.html</a></p>
<blockquote>
<p>The AWS Backup console allows you to restore Amazon EC2 recovery points with the following parameters and settings you can customize:</p>
<ul>
<li><p>Instance type</p>
</li>
<li><p>Amazon VPC</p>
</li>
<li><p>Subnet</p>
</li>
<li><p>Security groups</p>
</li>
<li><p>IAM role</p>
</li>
<li><p>Shutdown behavior</p>
</li>
<li><p>Stop–hibernate behavior</p>
</li>
<li><p>Termination protection</p>
</li>
<li><p>T2/T3 unlimited</p>
</li>
<li><p>Placement group name</p>
</li>
<li><p>EBS-optimized instance</p>
</li>
<li><p>Tenancy</p>
</li>
<li><p>RAM disk ID</p>
</li>
<li><p>Kernel ID</p>
</li>
<li><p>User data</p>
</li>
<li><p>Deletion on termination</p>
</li>
</ul>
</blockquote>
<p>To restore an EC2 instance with other customized parameters, including the network interface, you must execute the StartRestoreJob API with metadata, e.g., from the AWS CLI or SDK.</p>
<blockquote>
<p><a target="_blank" href="https://docs.aws.amazon.com/aws-backup/latest/devguide/restoring-ec2.html#restoring-ec2-cli"><strong>Use the AWS Backup API, CLI, or SDK to restore Amazon EC2 recovery points</strong></a> Use StartRestoreJob. This option allows you to restore all 38 parameters, including the 22 parameters that are not customizable on the console.</p>
</blockquote>
<p>The following is an example of execution with the AWS CLI.</p>
<pre><code class="lang-bash">$ aws backup start-restore-job \
  --recovery-point-arn <span class="hljs-string">"arn:aws:ec2:us-west-2::image/ami-xxxxxxxxxxxxxxxxx"</span> \
  --iam-role-arn <span class="hljs-string">"arn:aws:iam::123456789012:role/service-role/AWSBackupDefaultServiceRole"</span> \
  --metadata file://metadata.json  
{
    <span class="hljs-string">"RestoreJobId"</span>: <span class="hljs-string">"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"</span>
}
</code></pre>
<p>You can specify parameters in metadata.json as follows.</p>
<h4 id="heading-example-of-specifying-a-private-ip-address">Example of specifying a private IP address</h4>
<p>Please note that if a backup source instance exists, the private IP address must be changed to avoid duplicate addresses.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"VpcId"</span>: <span class="hljs-string">"vpc-xxxxxxxxxxxxxxxxx"</span>,
    <span class="hljs-attr">"Monitoring"</span>: <span class="hljs-string">"{\"State\":\"disabled\"}"</span>,
    <span class="hljs-attr">"CapacityReservationSpecification"</span>: <span class="hljs-string">"{\"CapacityReservationPreference\":\"open\"}"</span>,
    <span class="hljs-attr">"InstanceInitiatedShutdownBehavior"</span>: <span class="hljs-string">"stop"</span>,
    <span class="hljs-attr">"DisableApiTermination"</span>: <span class="hljs-string">"false"</span>,
    <span class="hljs-attr">"CreditSpecification"</span>: <span class="hljs-string">"{\"CpuCredits\":\"unlimited\"}"</span>,
    <span class="hljs-attr">"HibernationOptions"</span>: <span class="hljs-string">"{\"Configured\":false}"</span>,
    <span class="hljs-attr">"EbsOptimized"</span>: <span class="hljs-string">"true"</span>,
    <span class="hljs-attr">"Placement"</span>: <span class="hljs-string">"{\"AvailabilityZone\":\"us-west-2d\",\"GroupName\":\"\",\"Tenancy\":\"default\"}"</span>,
    <span class="hljs-attr">"aws:backup:request-id"</span>: <span class="hljs-string">"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"</span>,
    <span class="hljs-attr">"InstanceType"</span>: <span class="hljs-string">"t4g.micro"</span>,
    <span class="hljs-attr">"NetworkInterfaces"</span>: <span class="hljs-string">"[{\"DeleteOnTermination\":true,\"Description\":\"\",\"DeviceIndex\":0,\"Groups\":[\"sg-xxxxxxxxxxxxxxxxx\"],\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"PrivateIpAddresses\":[{\"Primary\":true,\"PrivateIpAddress\":\"172.31.62.169\"}],\"SubnetId\":\"subnet-xxxxxxxxxxxxxxxxx\",\"InterfaceType\":\"interface\"},{\"DeleteOnTermination\":false,\"Description\":\"\",\"DeviceIndex\":1,\"Groups\":[\"sg-xxxxxxxxxxxxxxxxx\"],\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"PrivateIpAddresses\":[{\"Primary\":true,\"PrivateIpAddress\":\"172.31.54.130\"}],\"SubnetId\":\"subnet-xxxxxxxxxxxxxxxxx\",\"InterfaceType\":\"interface\"}]"</span>
}
</code></pre>
<h4 id="heading-example-of-specifying-eni-id">Example of specifying ENI-ID</h4>
<p>Please note that the ENI must be detached from the instance and in Available status if the backup source ENI is to be used.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"VpcId"</span>: <span class="hljs-string">"vpc-xxxxxxxxxxxxxxxxx"</span>,
    <span class="hljs-attr">"Monitoring"</span>: <span class="hljs-string">"{\"State\":\"disabled\"}"</span>,
    <span class="hljs-attr">"CapacityReservationSpecification"</span>: <span class="hljs-string">"{\"CapacityReservationPreference\":\"open\"}"</span>,
    <span class="hljs-attr">"InstanceInitiatedShutdownBehavior"</span>: <span class="hljs-string">"stop"</span>,
    <span class="hljs-attr">"DisableApiTermination"</span>: <span class="hljs-string">"false"</span>,
    <span class="hljs-attr">"CreditSpecification"</span>: <span class="hljs-string">"{\"CpuCredits\":\"unlimited\"}"</span>,
    <span class="hljs-attr">"HibernationOptions"</span>: <span class="hljs-string">"{\"Configured\":false}"</span>,
    <span class="hljs-attr">"EbsOptimized"</span>: <span class="hljs-string">"true"</span>,
    <span class="hljs-attr">"Placement"</span>: <span class="hljs-string">"{\"AvailabilityZone\":\"us-west-2d\",\"GroupName\":\"\",\"Tenancy\":\"default\"}"</span>,
    <span class="hljs-attr">"aws:backup:request-id"</span>: <span class="hljs-string">"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"</span>,
    <span class="hljs-attr">"InstanceType"</span>: <span class="hljs-string">"t4g.micro"</span>,
    <span class="hljs-attr">"NetworkInterfaces"</span>: <span class="hljs-string">"[{\"DeleteOnTermination\":true,\"Description\":\"\",\"DeviceIndex\":0,\"Groups\":[\"sg-xxxxxxxxxxxxxxxxx\"],\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"PrivateIpAddresses\":[{\"Primary\":true,\"PrivateIpAddress\":\"172.31.62.169\"}],\"SubnetId\":\"subnet-xxxxxxxxxxxxxxxxx\",\"InterfaceType\":\"interface\"},{\"DeleteOnTermination\":false,\"Description\":\"\",\"DeviceIndex\":1,\"Ipv6AddressCount\":0,\"Ipv6Addresses\":[],\"NetworkInterfaceId\":\"eni-bbbbbbbbbbbbbbbbb\"}]"</span>
}
</code></pre>
<h2 id="heading-reference">Reference</h2>
<p><a target="_blank" href="https://aws.amazon.com/premiumsupport/knowledge-center/aws-backup-ec2-restore-cli/?nc1=h_ls">https://aws.amazon.com/premiumsupport/knowledge-center/aws-backup-ec2-restore-cli/</a></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[How to use AWS Parameters and Secrets Lambda Extension]]></title><description><![CDATA[What is AWS Parameters and Secrets Lambda Extension?
https://aws.amazon.com/jp/about-aws/whats-new/2022/10/aws-parameters-secrets-lambda-extension/
This extension can be used to retrieve parameters from the AWS Systems Manager Parameter Store and sec...]]></description><link>https://hayao-k.dev/how-to-use-aws-parameters-and-secrets-lambda-extension</link><guid isPermaLink="true">https://hayao-k.dev/how-to-use-aws-parameters-and-secrets-lambda-extension</guid><category><![CDATA[Lambda Extension]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[systems manager]]></category><category><![CDATA[secretmanager]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Wed, 19 Oct 2022 08:07:06 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-what-is-aws-parameters-and-secrets-lambda-extension">What is AWS Parameters and Secrets Lambda Extension?</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://aws.amazon.com/jp/about-aws/whats-new/2022/10/aws-parameters-secrets-lambda-extension/">https://aws.amazon.com/jp/about-aws/whats-new/2022/10/aws-parameters-secrets-lambda-extension/</a></div>
<p>This extension can be used to retrieve parameters from the AWS Systems Manager Parameter Store and secrets from the AWS Secrets Manager.</p>
<h2 id="heading-what-makes-you-happy">What makes you happy?</h2>
<p>Until now, parameters and secrets were obtained in the Lambda function process using the AWS SDK or other means. </p>
<p>With this extension, these values can be cached and reused during the lifecycle of a Lambda function. This reduces the latency and cost of retrieving parameters and secrets.</p>
<h2 id="heading-basic-usage">Basic usage</h2>
<p>Please refer to each documents for details.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html
">https://docs.aws.amazon.com/systems-manager/latest/userguide/ps-integration-lambda-extensions.html
</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html">https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html</a></div>
<h3 id="heading-set-layer-of-the-extension-to-lambda-function">Set Layer of the extension to Lambda function</h3>
<p>Lambda Extension is made available by configuring Lambda Layers. In Managed Console, AWS Parameters and Secrets Lambda Extension could be selected in the AWS layer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666165829065/jrHRqONjJ.png" alt="image.png" />
mage.png</p>
<p>When configuring from the CLI or other means, specify the ARN of the published Layer. A list of ARNs for each region is provided in the documentation.</p>
<h3 id="heading-write-http-get-code-in-the-function">Write HTTP GET code in the function</h3>
<p>Using this Extension eliminates processing in the AWS SDK, but the code to retrieve the value with an HTTP GET request is still required. See the second half of this post for the sample code.</p>
<h3 id="heading-change-iam-policy-for-execution-role">Change IAM policy for execution role</h3>
<p>The extension uses the credentials of the IAM role used to execute the Lambda function itself. Therefore, an appropriate IAM policy must be set up to retrieve parameters and secrets. For example, for the Parameter Store, ssm:GetParameter and kms:Decrypt (when using SecureString) are required.</p>
<h3 id="heading-optional-set-environment-variables-for-functions">(Optional) Set environment variables for functions</h3>
<p>TTL for the cache, log level, etc., can be controlled by setting environment variables for the Lambda function.</p>
<h2 id="heading-sample-code">Sample Code</h2>
<p>This is an example of referencing Amazon Linux 2 AMI public parameters.</p>
<p>Notes are as follows.</p>
<ul>
<li><code>/</code> in the parameter name must be encoded</li>
<li>The extension's local HTTP server port starts at default 2773<ul>
<li>It can be changed via the environment variable PARAMETERS_SECRETS_EXTENSION_HTTP_PORT</li>
</ul>
</li>
<li>Header 'X-Aws-Parameters-Secrets-Token' with AWS_SESSION_TOKEN environment variable must be added<ul>
<li>If not specified, it will be 401 unauthorized.</li>
</ul>
</li>
</ul>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> https = <span class="hljs-built_in">require</span>(<span class="hljs-string">'http'</span>);

<span class="hljs-built_in">exports</span>.handler = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event, context, callback</span>) </span>{

    <span class="hljs-keyword">const</span> options = {
        <span class="hljs-attr">hostname</span>: <span class="hljs-string">'localhost'</span>,
        <span class="hljs-attr">port</span>: <span class="hljs-number">2773</span>,
        <span class="hljs-attr">path</span>: <span class="hljs-string">'/systemsmanager/parameters/get/?name=%2Faws%2Fservice%2Fami-amazon-linux-latest%2Famzn-ami-hvm-x86_64-gp2'</span>,
        <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">'X-Aws-Parameters-Secrets-Token'</span>: process.env.AWS_SESSION_TOKEN
        },
        <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>
    };

    <span class="hljs-keyword">const</span> req = https.request(options, <span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
        res.on(<span class="hljs-string">'data'</span>, <span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Response from cache: "</span>+d);
            <span class="hljs-keyword">return</span> d;
        });
    });

    req.on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
        <span class="hljs-built_in">console</span>.error(error);
    });

    req.end();
};
</code></pre>
<p>The log of the execution result looks like this You got the parameter values!</p>
<pre><code>[AWS Parameters and Secrets Lambda Extension] <span class="hljs-number">2022</span>/<span class="hljs-number">10</span>/<span class="hljs-number">19</span> <span class="hljs-number">06</span>:<span class="hljs-number">51</span>:<span class="hljs-number">08</span> PARAMETERS_SECRETS_EXTENSION_LOG_LEVEL is not present. Log level set to info.
[AWS Parameters and Secrets Lambda Extension] <span class="hljs-number">2022</span>/<span class="hljs-number">10</span>/<span class="hljs-number">19</span> <span class="hljs-number">06</span>:<span class="hljs-number">51</span>:<span class="hljs-number">08</span> INFO Systems Manager Parameter Store and Secrets Manager Lambda Extension <span class="hljs-number">1.0</span><span class="hljs-number">.94</span>
[AWS Parameters and Secrets Lambda Extension] <span class="hljs-number">2022</span>/<span class="hljs-number">10</span>/<span class="hljs-number">19</span> <span class="hljs-number">06</span>:<span class="hljs-number">51</span>:<span class="hljs-number">08</span> INFO Serving on port <span class="hljs-number">2773</span>
EXTENSION    Name: AWSParametersAndSecretsLambdaExtension    State: Ready    Events: [INVOKE,SHUTDOWN]
START RequestId: bb5bcc53<span class="hljs-number">-38</span>cc<span class="hljs-number">-42</span>d7<span class="hljs-number">-9</span>dc5-xxxxxxxxxxxx Version: $LATEST
[AWS Parameters and Secrets Lambda Extension] <span class="hljs-number">2022</span>/<span class="hljs-number">10</span>/<span class="hljs-number">19</span> <span class="hljs-number">06</span>:<span class="hljs-number">51</span>:<span class="hljs-number">08</span> INFO ready to serve traffic
<span class="hljs-number">2022</span><span class="hljs-number">-10</span><span class="hljs-number">-19</span>T06:<span class="hljs-number">51</span>:<span class="hljs-number">09.247</span>Z    bb5bcc53<span class="hljs-number">-38</span>cc<span class="hljs-number">-42</span>d7<span class="hljs-number">-9</span>dc5-xxxxxxxxxxxx    INFO    Response <span class="hljs-keyword">from</span> cache: {<span class="hljs-string">"Parameter"</span>:{<span class="hljs-string">"ARN"</span>:<span class="hljs-string">"arn:aws:ssm:ap-northeast-1::parameter/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2"</span>,<span class="hljs-string">"DataType"</span>:<span class="hljs-string">"text"</span>,<span class="hljs-string">"LastModifiedDate"</span>:<span class="hljs-string">"2022-10-04T17:56:51.889Z"</span>,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2"</span>,<span class="hljs-string">"Selector"</span>:<span class="hljs-literal">null</span>,<span class="hljs-string">"SourceResult"</span>:<span class="hljs-literal">null</span>,<span class="hljs-string">"Type"</span>:<span class="hljs-string">"String"</span>,<span class="hljs-string">"Value"</span>:<span class="hljs-string">"ami-0fb16641312307fa9"</span>,<span class="hljs-string">"Version"</span>:<span class="hljs-number">49</span>},<span class="hljs-string">"ResultMetadata"</span>:{}}
END RequestId: bb5bcc53<span class="hljs-number">-38</span>cc<span class="hljs-number">-42</span>d7<span class="hljs-number">-9</span>dc5-xxxxxxxxxxxx
REPORT RequestId: bb5bcc53<span class="hljs-number">-38</span>cc<span class="hljs-number">-42</span>d7<span class="hljs-number">-9</span>dc5-xxxxxxxxxxxx    Duration: <span class="hljs-number">796.05</span> ms    Billed Duration: <span class="hljs-number">797</span> ms    Memory Size: <span class="hljs-number">128</span> MB    Max Memory Used: <span class="hljs-number">76</span> MB    Init Duration: <span class="hljs-number">324.74</span> ms
</code></pre><p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Backup AWS Support case history to your Wiki]]></title><description><![CDATA[Why do you need a backup?
AWS Support case histories are retained for up to 12 months.
https://aws.amazon.com/premiumsupport/faqs/ 

Q: How long is case history retained?Case history information is available for 12 months after creation.

Therefore, ...]]></description><link>https://hayao-k.dev/backup-aws-support-case-history-to-your-wiki</link><guid isPermaLink="true">https://hayao-k.dev/backup-aws-support-case-history-to-your-wiki</guid><category><![CDATA[AWS]]></category><category><![CDATA[Python]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[eventbridge]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Tue, 27 Sep 2022 02:49:29 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-why-do-you-need-a-backup">Why do you need a backup?</h2>
<p>AWS Support case histories are retained for up to 12 months.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://aws.amazon.com/premiumsupport/faqs/ ">https://aws.amazon.com/premiumsupport/faqs/ </a></div>
<blockquote>
<p><strong>Q: How long is case history retained?</strong><br />Case history information is available for 12 months after creation.</p>
</blockquote>
<p>Therefore, if you want to store the answers as knowledge in the long term, you need a backup.</p>
<p>This post summarises how to convert support case answers into markdown format and post them to your Wiki via the API.</p>
<h2 id="heading-architecture">Architecture</h2>
<ul>
<li>Detecting support case closures with EventBridge event rules</li>
<li>Use AWS Support API in AWS Lambda to get case details</li>
<li>Formatted into Markdown format and posted to your Wiki</li>
</ul>
<p><strong>Note:</strong>  Using AWS Support API requires a subscription to one of the following support plans: Business, Enterprise On-Ramp, or Enterprise.</p>
<p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/acadaece-2321-160c-8b57-dec24d54c944.png" alt="image.png" /></p>
<p>This post is about GROWI, the OSS wiki, but it can be applied to any software or service that can post via API.</p>
<p>https://growi.org/en/</p>
<h2 id="heading-points-for-implementation">Points for Implementation</h2>
<h3 id="heading-eventbridge-event-rule">EventBridge event rule</h3>
<p>Set to detect events where event-name is <code>ResolveCase</code>.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"source"</span>: [<span class="hljs-string">"aws.support"</span>],
  <span class="hljs-attr">"detail-type"</span>: [<span class="hljs-string">"Support Case Update"</span>],
  <span class="hljs-attr">"detail"</span>: {
    <span class="hljs-attr">"event-name"</span>: [<span class="hljs-string">"ResolveCase"</span>]
  }
}
</code></pre>
<p>See documentation for examples of events.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/awssupport/latest/user/event-bridge-support.html#example-event-logs-for-support-cases">https://docs.aws.amazon.com/awssupport/latest/user/event-bridge-support.html#example-event-logs-for-support-cases</a></div>
<pre><code class="lang-json">{
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0"</span>,
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"1aa4458d-556f-732e-ddc1-4a5b2fbd14a5"</span>,
    <span class="hljs-attr">"detail-type"</span>: <span class="hljs-string">"Support Case Update"</span>,
    <span class="hljs-attr">"source"</span>: <span class="hljs-string">"aws.support"</span>,
    <span class="hljs-attr">"account"</span>: <span class="hljs-string">"111122223333"</span>,
    <span class="hljs-attr">"time"</span>: <span class="hljs-string">"2022-02-21T15:51:31Z"</span>,
    <span class="hljs-attr">"region"</span>: <span class="hljs-string">"us-east-1"</span>,
    <span class="hljs-attr">"resources"</span>: [],
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"case-id"</span>: <span class="hljs-string">"case-111122223333-muen-2022-7118885805350839"</span>,
        <span class="hljs-attr">"display-id"</span>: <span class="hljs-string">"1234563851"</span>,
        <span class="hljs-attr">"communication-id"</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">"event-name"</span>: <span class="hljs-string">"ResolveCase"</span>,
        <span class="hljs-attr">"origin"</span>: <span class="hljs-string">""</span>
    }
}
</code></pre>
<h3 id="heading-getting-support-case-information">Getting support case information</h3>
<p>The event passed from EventBrige contains a Case ID, so use <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/support.html#Support.Client.describe_cases">DescribeCases API</a> to get the details. Communications is obtained separately, so <code>includeCommunications</code> should be False.</p>
<pre><code class="lang-py"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">describe_case</span>(<span class="hljs-params">case_id</span>):</span>
    response = support.describe_cases(
        caseIdList=[
            case_id
        ],
        includeResolvedCases=<span class="hljs-literal">True</span>,
        includeCommunications=<span class="hljs-literal">False</span>
    )
    <span class="hljs-keyword">return</span> response[<span class="hljs-string">'cases'</span>][<span class="hljs-number">0</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    case_info = describe_case(event[<span class="hljs-string">'detail'</span>][<span class="hljs-string">'case-id'</span>])
</code></pre>
<h3 id="heading-cases-excluded-from-backup">Cases excluded from backup</h3>
<p>Increased service quotas and Enterprise support activation would not require backup.</p>
<pre><code class="lang-py"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    case_info = describe_case(event[<span class="hljs-string">'detail'</span>][<span class="hljs-string">'case-id'</span>])

    <span class="hljs-keyword">if</span> case_info[<span class="hljs-string">'serviceCode'</span>] == <span class="hljs-string">"service-limit-increase"</span> <span class="hljs-keyword">or</span> \
       case_info[<span class="hljs-string">'subject'</span>] == <span class="hljs-string">"Enterprise Activation Request for Linked account."</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">'Result'</span>: <span class="hljs-string">'Service limit increase or Enterprise support activation will not be posted.'</span>
        }
</code></pre>
<h3 id="heading-get-communication-history-with-support">Get communication history with support</h3>
<p>Use <a target="_blank" href="https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/support.html#Support.Client.describe_communications">DescribeCommunications API</a>. The data is retrieved from the most recent communication, so the order is reordered and concatenated.</p>
<p>Certain symbols of three or more consecutive characters are displayed as headers or horizontal lines in Markdown notation. They are replaced to avoid involuntary conversions.</p>
<pre><code class="lang-py"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">describe_communications</span>(<span class="hljs-params">case_id</span>):</span>
    body = <span class="hljs-string">""</span>
    paginator = support.get_paginator(<span class="hljs-string">'describe_communications'</span>)
    page_iterator = paginator.paginate(caseId=case_id)

    <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> page_iterator:
        <span class="hljs-keyword">for</span> communication <span class="hljs-keyword">in</span> page[<span class="hljs-string">'communications'</span>]:
            body = re.sub(
                <span class="hljs-string">r'-{3,}|={3,}|\*{3,}|_{3,}'</span>, <span class="hljs-string">"..."</span>, communication[<span class="hljs-string">'body'</span>]
            ) + <span class="hljs-string">'\n\n---\n\n'</span> + body

    communications = <span class="hljs-string">'## Communications\n'</span> + body
    <span class="hljs-keyword">return</span> communications
</code></pre>
<h3 id="heading-post-to-growi">Post to GROWI</h3>
<p>The API reference is below.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.growi.org/en/api/rest-v3.html">https://docs.growi.org/en/api/rest-v3.html</a></div>
<p>New pages are created using <code>createPage</code> (POST <code>/pages</code>).</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"body"</span>: <span class="hljs-string">"string"</span>,
  <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"grant"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"grantUserGroupId"</span>: <span class="hljs-string">"5ae5fccfc5577b0004dbd8ab"</span>,
  <span class="hljs-attr">"pageTags"</span>: [
    {
      <span class="hljs-attr">"_id"</span>: <span class="hljs-string">"5e2d6aede35da4004ef7e0b7"</span>,
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"daily"</span>,
      <span class="hljs-attr">"count"</span>: <span class="hljs-number">3</span>
    }
  ],
  <span class="hljs-attr">"createFromPageTree"</span>: <span class="hljs-literal">true</span>
}
</code></pre>
<ul>
<li>Only <code>body</code> (the body of the article) and <code>path</code> (the path to the post) are required.</li>
<li>The <code>grant</code> specifies the extent to which the page is public (1: public, 2: only people who know the link, etc.).</li>
<li>In practice, you should also include the <code>access_token</code> (api_key).</li>
</ul>
<pre><code class="lang-py"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_payload</span>(<span class="hljs-params">account_id, case_info</span>):</span>
    token = os.environ[<span class="hljs-string">'API_KEY'</span>]
    title = <span class="hljs-string">'# '</span> + case_info[<span class="hljs-string">'subject'</span>] + <span class="hljs-string">'\n'</span>
    information = <span class="hljs-string">'## Case Information\n'</span> + \
        <span class="hljs-string">'* Account ID: '</span> + account_id + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Case ID: '</span> + case_info[<span class="hljs-string">'displayId'</span>] + <span class="hljs-string">'\n'</span> +\
        <span class="hljs-string">'* Create Date '</span> + case_info[<span class="hljs-string">'timeCreated'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Severity: '</span> + case_info[<span class="hljs-string">'severityCode'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Service: '</span> + case_info[<span class="hljs-string">'serviceCode'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Category: '</span> + case_info[<span class="hljs-string">'categoryCode'</span>] + <span class="hljs-string">'\n'</span>
    communications = describe_communications(case_info[<span class="hljs-string">'caseId'</span>])
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'access_token'</span>: token,
        <span class="hljs-string">'path'</span>: <span class="hljs-string">'/PathYouWantToPost/'</span> + case_info[<span class="hljs-string">'subject'</span>],
        <span class="hljs-string">'body'</span>: title + information + communications,
        <span class="hljs-string">'grant'</span>: <span class="hljs-number">1</span>,
    }

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    case_info = describe_case(event[<span class="hljs-string">'detail'</span>][<span class="hljs-string">'case-id'</span>])
    payload = create_payload(event[<span class="hljs-string">'account'</span>], case_info)
    url = os.environ[<span class="hljs-string">'API_URL'</span>]
    headers = {
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
    }
    req = Request(url, json.dumps(payload).encode(<span class="hljs-string">'utf-8'</span>), headers)
</code></pre>
<h2 id="heading-lambda-function-example">Lambda function example</h2>
<p>The function execution role must be granted AWS Support referencing rights.</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> logging <span class="hljs-keyword">import</span> getLogger, INFO
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> urllib.request <span class="hljs-keyword">import</span> Request, urlopen
<span class="hljs-keyword">from</span> urllib.error <span class="hljs-keyword">import</span> URLError, HTTPError
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">from</span> botocore.exceptions <span class="hljs-keyword">import</span> ClientError
<span class="hljs-keyword">import</span> boto3

logger = getLogger()
logger.setLevel(INFO)

support = boto3.client(<span class="hljs-string">'support'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">describe_communications</span>(<span class="hljs-params">case_id</span>):</span>
    body = <span class="hljs-string">''</span>
    <span class="hljs-keyword">try</span>:
        paginator = support.get_paginator(<span class="hljs-string">'describe_communications'</span>)
        page_iterator = paginator.paginate(caseId=case_id)
    <span class="hljs-keyword">except</span> ClientError <span class="hljs-keyword">as</span> err:
        logger.error(err.response[<span class="hljs-string">'Error'</span>][<span class="hljs-string">'Message'</span>])
        <span class="hljs-keyword">raise</span>

    <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> page_iterator:
        <span class="hljs-keyword">for</span> communication <span class="hljs-keyword">in</span> page[<span class="hljs-string">'communications'</span>]:
            body = re.sub(
                <span class="hljs-string">r'-{3,}|={3,}|\*{3,}|_{3,}'</span>, <span class="hljs-string">"..."</span>, communication[<span class="hljs-string">'body'</span>]
            ) + <span class="hljs-string">'\n\n---\n\n'</span> + body

    communications = <span class="hljs-string">'## Communications\n'</span> + body
    <span class="hljs-keyword">return</span> communications

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_payload</span>(<span class="hljs-params">account_id, case_info</span>):</span>
    token = os.environ[<span class="hljs-string">'API_KEY'</span>]
    title = <span class="hljs-string">'# '</span> + case_info[<span class="hljs-string">'subject'</span>] + <span class="hljs-string">'\n'</span>
    information = <span class="hljs-string">'## Case Information\n'</span> + \
        <span class="hljs-string">'* Account ID: '</span> + account_id + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Case ID: '</span> + case_info[<span class="hljs-string">'displayId'</span>] + <span class="hljs-string">'\n'</span> +\
        <span class="hljs-string">'* Create Date '</span> + case_info[<span class="hljs-string">'timeCreated'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Severity: '</span> + case_info[<span class="hljs-string">'severityCode'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Service: '</span> + case_info[<span class="hljs-string">'serviceCode'</span>] + <span class="hljs-string">'\n'</span> + \
        <span class="hljs-string">'* Category: '</span> + case_info[<span class="hljs-string">'categoryCode'</span>] + <span class="hljs-string">'\n'</span>
    communications = describe_communications(case_info[<span class="hljs-string">'caseId'</span>])
    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">'access_token'</span>: token,
        <span class="hljs-string">'path'</span>: <span class="hljs-string">'/PathYouWantToPost/'</span> + case_info[<span class="hljs-string">'subject'</span>],
        <span class="hljs-string">'body'</span>: title + information + communications,
        <span class="hljs-string">'grant'</span>: <span class="hljs-number">1</span>,
    }

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">describe_case</span>(<span class="hljs-params">case_id</span>):</span>
    <span class="hljs-keyword">try</span>:
        response = support.describe_cases(
            caseIdList=[
                case_id
            ],
            includeResolvedCases=<span class="hljs-literal">True</span>,
            includeCommunications=<span class="hljs-literal">False</span>
        )
    <span class="hljs-keyword">except</span> ClientError <span class="hljs-keyword">as</span> err:
        logger.error(err.response[<span class="hljs-string">'Error'</span>][<span class="hljs-string">'Message'</span>])
        <span class="hljs-keyword">raise</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> response[<span class="hljs-string">'cases'</span>][<span class="hljs-number">0</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    case_info = describe_case(event[<span class="hljs-string">'detail'</span>][<span class="hljs-string">'case-id'</span>])

    <span class="hljs-keyword">if</span> case_info[<span class="hljs-string">'serviceCode'</span>] == <span class="hljs-string">"service-limit-increase"</span> <span class="hljs-keyword">or</span> \
       case_info[<span class="hljs-string">'subject'</span>] == <span class="hljs-string">"Enterprise Activation Request for Linked account."</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">'Result'</span>: <span class="hljs-string">'Service limit increase or Enterprise support activation will not be posted.'</span>
        }

    payload = create_payload(event[<span class="hljs-string">'account'</span>], case_info)
    url = os.environ[<span class="hljs-string">'API_URL'</span>]
    headers = {
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
    }
    req = Request(url, json.dumps(payload).encode(<span class="hljs-string">'utf-8'</span>), headers)

    <span class="hljs-keyword">try</span>:
        response = urlopen(req)
        response.read()
    <span class="hljs-keyword">except</span> HTTPError <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">'Result'</span>: <span class="hljs-string">f'''Request failed: <span class="hljs-subst">{e.code}</span>, <span class="hljs-subst">{e.reason}</span>)'''</span>
        }
    <span class="hljs-keyword">except</span> URLError <span class="hljs-keyword">as</span> e:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">'Result'</span>: <span class="hljs-string">f'''Request failed: <span class="hljs-subst">{e.reason}</span>)'''</span>
        }
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">'Result'</span> : <span class="hljs-string">'Knowledge posted.'</span>
        }
</code></pre>
<p>The results of the POST are as follows. Sorry, I can't share the case's contents, so it's mostly blacked out.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1664246588427/5Xc66-qVZ.png" alt="image.png" /></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Migrating CloudFront OAI to OAC using CloudFormation]]></title><description><![CDATA[Goals of this post
Describes the CloudFormation template modifications required to migrate CloudFront's Origin access identity (OAI) to Origin Access Control (OAC).
OAC is a new access control method for setting S3 buckets as origins in CloudFront.
h...]]></description><link>https://hayao-k.dev/migrating-cloudfront-oai-to-oac-using-cloudformation</link><guid isPermaLink="true">https://hayao-k.dev/migrating-cloudfront-oai-to-oac-using-cloudformation</guid><category><![CDATA[AWS]]></category><category><![CDATA[cloudformation]]></category><category><![CDATA[cloudfront]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Wed, 21 Sep 2022 11:55:50 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-goals-of-this-post">Goals of this post</h2>
<p>Describes the CloudFormation template modifications required to migrate CloudFront's Origin access identity (OAI) to Origin Access Control (OAC).</p>
<p>OAC is a new access control method for setting S3 buckets as origins in CloudFront.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://aws.amazon.com/jp/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/">https://aws.amazon.com/jp/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/</a></div>
<p>Previously we had used Origin Access Identity (OAI) to restrict access to origin S3 buckets to CloudFront only.</p>
<p>OAI is currently treated as <strong>Legacy</strong>. Migration from OAI to OAC is recommended to support security best practices and new regions.</p>
<h2 id="heading-sample-template">Sample Template</h2>
<p>See my GitHub repository!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/CFn-sample-cloudfront-oac">https://github.com/hayao-k/CFn-sample-cloudfront-oac</a></div>
<h2 id="heading-examples-of-modifications">Examples of modifications</h2>
<p>Migrating existing CloudFormation templates from OAI to OAC requires, as a minimum, the following modifications.</p>
<ul>
<li>Creating  an OAC</li>
<li>Modifying the bucket policy</li>
<li>Modifying Distribution Origin</li>
</ul>
<h3 id="heading-creating-an-oac">Creating an OAC</h3>
<p>Create an <code>AWS::CloudFront::OriginAccessControl</code>. Description of the OriginAccessControlConfig is an optional field.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">CloudFrontOriginAccessControl:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::CloudFront::OriginAccessControl</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">OriginAccessControlConfig:</span>
        <span class="hljs-attr">Description:</span> <span class="hljs-string">Default</span> <span class="hljs-string">Origin</span> <span class="hljs-string">Access</span> <span class="hljs-string">Control</span>
        <span class="hljs-attr">Name:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AWS::StackName</span>
        <span class="hljs-attr">OriginAccessControlOriginType:</span> <span class="hljs-string">s3</span>
        <span class="hljs-attr">SigningBehavior:</span> <span class="hljs-string">always</span>
        <span class="hljs-attr">SigningProtocol:</span> <span class="hljs-string">sigv4</span>
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html">https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originaccesscontrol.html</a></div>
<p>Delete <code>AWS::CloudFront::CloudFrontOriginAccessIdentity</code> as it will no longer be required after migration.</p>
<h3 id="heading-modifying-the-bucket-policy">Modifying the bucket policy</h3>
<p>In the following example, the policy for OAI has been removed, but it is a recommended migration procedure to include both OAI and OAC policies. This will prevent CloudFront from losing access to S3 buckets during migration to OAC. Please take action as necessary.</p>
<p><strong>Before</strong></p>
<pre><code class="lang-yaml"> <span class="hljs-comment"># S3 bucket policy to allow access from CloudFront OAI</span>
  <span class="hljs-attr">AssetsBucketPolicy:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::BucketPolicy</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Bucket:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AssetsBucket</span>
      <span class="hljs-attr">PolicyDocument:</span>
        <span class="hljs-attr">Statement:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Action:</span> <span class="hljs-string">s3:GetObject</span>
          <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
          <span class="hljs-attr">Resource:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AssetsBucket.Arn}/*</span>
          <span class="hljs-attr">Principal:</span>
            <span class="hljs-attr">AWS:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">arn:aws:iam::cloudfront:user/CloudFront</span> <span class="hljs-string">Origin</span> <span class="hljs-string">Access</span> <span class="hljs-string">Identity</span> <span class="hljs-string">${CloudFrontOriginAccessIdentity}</span>
</code></pre>
<p><strong>After</strong></p>
<pre><code class="lang-yaml"><span class="hljs-comment"># S3 bucket policy to allow access from CloudFront OAC</span>
  <span class="hljs-attr">AssetsBucketPolicy:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::S3::BucketPolicy</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Bucket:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">AssetsBucket</span>
      <span class="hljs-attr">PolicyDocument:</span>
        <span class="hljs-attr">Statement:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Action:</span> <span class="hljs-string">s3:GetObject</span>
          <span class="hljs-attr">Effect:</span> <span class="hljs-string">Allow</span>
          <span class="hljs-attr">Resource:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">${AssetsBucket.Arn}/*</span>
          <span class="hljs-attr">Principal:</span>
            <span class="hljs-attr">Service:</span> <span class="hljs-string">cloudfront.amazonaws.com</span>
          <span class="hljs-attr">Condition:</span>
            <span class="hljs-attr">StringEquals:</span>
              <span class="hljs-attr">AWS:SourceArn:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}</span>
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html">https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html</a></div>
<h2 id="heading-modifying-distribution-origin">Modifying Distribution Origin</h2>
<p>OAI sets S3OriginConfig for Distribution Origin, but OAC requires OriginAccessControlId to be specified.</p>
<p><strong>Before</strong></p>
<pre><code class="lang-yaml">  <span class="hljs-attr">AssetsDistribution:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::CloudFront::Distribution</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">DistributionConfig:</span>
        <span class="hljs-attr">Origins:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Id:</span> <span class="hljs-string">S3Origin</span>
          <span class="hljs-attr">DomainName:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AssetsBucket.DomainName</span>
          <span class="hljs-attr">S3OriginConfig:</span>
            <span class="hljs-attr">OriginAccessIdentity:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}</span>
</code></pre>
<p><strong>After</strong></p>
<pre><code class="lang-yaml">  <span class="hljs-attr">AssetsDistribution:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::CloudFront::Distribution</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">DistributionConfig:</span>
        <span class="hljs-attr">Origins:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">Id:</span> <span class="hljs-string">S3Origin</span>
          <span class="hljs-attr">DomainName:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">AssetsBucket.DomainName</span>
          <span class="hljs-attr">S3OriginConfig:</span>
            <span class="hljs-attr">OriginAccessIdentity:</span> <span class="hljs-string">''</span>
          <span class="hljs-attr">OriginAccessControlId:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">CloudFrontOriginAccessControl.Id</span>
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html">https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-origin.html</a></div>
<p><strong>Note</strong>: As of September 2022, an empty OriginAccessIdentity must be specified in S3OriginConfig.</p>
<p>Deleting S3OriginConfig will result in the following error when updating the stack.</p>
<pre><code>Resource <span class="hljs-keyword">handler</span> returned message: "Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified"
</code></pre><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663746850244/IDxEAq0v9.png" alt="image.png" /></p>
<h2 id="heading-examples-of-actual-migration">Examples of actual migration</h2>
<p>Now let's update the existing CloudFormation stack using the <a target="_blank" href="https://github.com/hayao-k/CFn-sample-cloudfront-oac/blob/main/CloudFront-S3-OAC.yaml">modified template</a>. Update the stack to ensure that the change set is as expected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663756931310/R7-Jqm-Oy.png" alt="image.png" /></p>
<p>As described before, removing the bucket policy for OAI and migrating to OAC will result in Access Denied during distribution updates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663760156996/Es-UXXUyL.png" alt="image.png" /></p>
<p>After updating the distribution, the S3 bucket access settings can be seen to have been updated to OAC.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663760434371/ZZE_cdyEy.png" alt="image.png" /></p>
<p>The S3 bucket policy is also updated as expected.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663760594959/Y5P_stKfL.png" alt="image.png" /></p>
<p>Also, access from the browser seems to be okay.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663760678612/CSIBmQzTY.png" alt="image.png" /></p>
<p>I hope this will be of help to someone else.</p>
]]></content:encoded></item><item><title><![CDATA[Get daily billing amounts by account with Cost Explorer API]]></title><description><![CDATA[Goal of this article
Use Cost Explorer API to get the daily billing amount for each account and output the following data in CSV.




Account IdAccount Name2022/4/12022/4/22022/4/3...2022/4/30



000000000000account-000042.79271652840.12471652743.123...]]></description><link>https://hayao-k.dev/get-daily-billing-amounts-by-account-with-cost-explorer-api</link><guid isPermaLink="true">https://hayao-k.dev/get-daily-billing-amounts-by-account-with-cost-explorer-api</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[cost-optimisation]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Sun, 26 Jun 2022 15:34:26 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-goal-of-this-article">Goal of this article</h2>
<p>Use Cost Explorer API to get the daily billing amount for each account and output the following data in CSV.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Account Id</td><td>Account Name</td><td>2022/4/1</td><td>2022/4/2</td><td>2022/4/3</td><td>...</td><td>2022/4/30</td></tr>
</thead>
<tbody>
<tr>
<td>000000000000</td><td>account-0000</td><td>42.792716528</td><td>40.124716527</td><td>43.123416527</td><td>...</td><td>50.922465287</td></tr>
<tr>
<td>111111111111</td><td>account-1111</td><td>32.263379809</td><td>30.235379809</td><td>31.263353594</td><td>...</td><td>22.133798094</td></tr>
<tr>
<td>222222222222</td><td>account-2222</td><td>751.71034839</td><td>720.51234839</td><td>772.62033294</td><td>...</td><td>651.71042035</td></tr>
<tr>
<td>333333333333</td><td>account-3333</td><td>4.6428</td><td>5.1234</td><td>7.8765</td><td>...</td><td>6.2234</td></tr>
<tr>
<td>444444444444</td><td>account-4444</td><td>407.74542211</td><td>420.12345211</td><td>395.12499518</td><td>...</td><td>417.99454118</td></tr>
<tr>
<td>555555555555</td><td>account-5555</td><td>386.78950595</td><td>400.12500509</td><td>352.89924506</td><td>...</td><td>370.75102656</td></tr>
<tr>
<td>...</td></tr>
</tbody>
</table>
</div><p>An equivalent CSV can be downloaded from the AWS Cost Explorer console. This post describes how to retrieve the data daily automatically.</p>
<h2 id="heading-example-api-request">Example API Request</h2>
<p>This is a minimal example of using AWS Lambda to retrieve the daily billing amount for the current month.</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">import</span> boto3

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    today = datetime.date.today()
    start = today.replace(day=<span class="hljs-number">1</span>).strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
    end = today.strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
    ce = boto3.client(<span class="hljs-string">'ce'</span>)
    response = ce.get_cost_and_usage(
        TimePeriod={
            <span class="hljs-string">'Start'</span>: start,
            <span class="hljs-string">'End'</span> :  end,
        },
        Granularity=<span class="hljs-string">'DAILY'</span>,
        Metrics=[
            <span class="hljs-string">'NetUnblendedCost'</span>
        ]
        GroupBy=[
            {
                <span class="hljs-string">'Type'</span>: <span class="hljs-string">'DIMENSION'</span>,
                <span class="hljs-string">'Key'</span>: <span class="hljs-string">'LINKED_ACCOUNT'</span>
            }
        ]
    )
    <span class="hljs-keyword">return</span> response[<span class="hljs-string">'ResultsByTime'</span>]
</code></pre>
<p>One point to note is that there is a time lag before the cost for a given day is determined. For example, if the cost for April 25 is obtained on April 26, it may be less than the actual billing amount.</p>
<p>The timing of when the AWS data will be updated is not disclosed, but it appears that on April 27, the costs for April 25 will be almost finalized.</p>
<h2 id="heading-processing-with-pandas">Processing with Pandas</h2>
<p>Since the API response is a nested JSON, we will consider an example of processing it into a CSV using Pandas.</p>
<p>When using Pandas with AWS Lambda, Lambda Layers must be used.</p>
<ul>
<li>Since each element contains billing data daily, it is processed one day at a time with a for statement.</li>
<li>After flattening the data using pandas.json_normalize, it is concatenated with the billing amount using pandas.concat.</li>
<li>After further renaming the column to the billing date, the results are merged using the Account Id as the key.</li>
</ul>
<pre><code class="lang-py">    merged_cost = pandas.DataFrame(
        index=[],
        columns=[<span class="hljs-string">'Account Id'</span>]
    )

    <span class="hljs-keyword">for</span> index, item <span class="hljs-keyword">in</span> enumerate(response):
        normalized_json = pandas.json_normalize(item[<span class="hljs-string">'Groups'</span>])
        split_keys = pandas.DataFrame(
            normalized_json[<span class="hljs-string">'Keys'</span>].tolist(),
            columns=[<span class="hljs-string">'Account Id'</span>]
        )
        cost = pandas.concat(
            [split_keys, normalized_json[<span class="hljs-string">'Metrics.NetUnblendedCost.Amount'</span>]],
            axis=<span class="hljs-number">1</span>
        )
        renamed_cost = cost.rename(
            columns={<span class="hljs-string">'Metrics.NetUnblendedCost.Amount'</span>: item[<span class="hljs-string">'TimePeriod'</span>][<span class="hljs-string">'Start'</span>]}
        )
        merged_cost = pandas.merge(merged_cost, renamed_cost, on=<span class="hljs-string">'Account Id'</span>, how=<span class="hljs-string">'right'</span>)

   print(merged_cost)
</code></pre>
<pre><code><span class="hljs-string">Account</span> <span class="hljs-string">Id</span>  <span class="hljs-string">...</span>     <span class="hljs-number">2022-04-25</span>
<span class="hljs-number">0</span>   <span class="hljs-number">000000000000</span>  <span class="hljs-string">...</span>  <span class="hljs-number">15.4985752779</span>
<span class="hljs-number">1</span>   <span class="hljs-number">111111111111</span>  <span class="hljs-string">...</span>         <span class="hljs-number">0.2176</span>
<span class="hljs-number">2</span>   <span class="hljs-number">222222222222</span>  <span class="hljs-string">...</span>   <span class="hljs-number">6.5567854795</span>
<span class="hljs-number">3</span>   <span class="hljs-number">333333333333</span>  <span class="hljs-string">...</span>   <span class="hljs-number">6.6300957379</span>
<span class="hljs-number">4</span>   <span class="hljs-number">444444444444</span>  <span class="hljs-string">...</span>   <span class="hljs-number">8.2720868504</span>
<span class="hljs-string">..</span>           <span class="hljs-string">...</span>  <span class="hljs-string">...</span>            <span class="hljs-string">...</span>
<span class="hljs-number">19</span>  <span class="hljs-number">777777777777</span>  <span class="hljs-string">...</span>  <span class="hljs-number">10.0121863554</span>
<span class="hljs-number">18</span>  <span class="hljs-number">888888888888</span>  <span class="hljs-string">...</span>   <span class="hljs-number">6.5976412116</span>
<span class="hljs-number">20</span>  <span class="hljs-number">999999999999</span>  <span class="hljs-string">...</span>    <span class="hljs-number">6.493243618</span>
[<span class="hljs-number">20</span> <span class="hljs-string">rows</span> <span class="hljs-string">x</span> <span class="hljs-number">26</span> <span class="hljs-string">columns</span>]
</code></pre><h2 id="heading-example-of-lambda-function">Example of Lambda function</h2>
<p>After processing with for statement, the list of account names obtained from AWS Organizations API is merged and output in CSV.</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> logging <span class="hljs-keyword">import</span> getLogger, INFO
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> pandas
<span class="hljs-keyword">from</span> botocore.exceptions <span class="hljs-keyword">import</span> ClientError

logger = getLogger()
logger.setLevel(INFO)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">upload_s3</span>(<span class="hljs-params">output, key, bucket</span>):</span>
    <span class="hljs-keyword">try</span>:
        s3_resource = boto3.resource(<span class="hljs-string">'s3'</span>)
        s3_bucket = s3_resource.Bucket(bucket)
        s3_bucket.upload_file(output, key, ExtraArgs={<span class="hljs-string">'ACL'</span>: <span class="hljs-string">'bucket-owner-full-control'</span>})
    <span class="hljs-keyword">except</span> ClientError <span class="hljs-keyword">as</span> err:
        logger.error(err.response[<span class="hljs-string">'Error'</span>][<span class="hljs-string">'Message'</span>])
        <span class="hljs-keyword">raise</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_ou_ids</span>(<span class="hljs-params">org, parent_id</span>):</span>
    ou_ids = []

    <span class="hljs-keyword">try</span>:
        paginator = org.get_paginator(<span class="hljs-string">'list_children'</span>)
        iterator = paginator.paginate(
            ParentId=parent_id,
            ChildType=<span class="hljs-string">'ORGANIZATIONAL_UNIT'</span>
        )
        <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> iterator:
            <span class="hljs-keyword">for</span> ou <span class="hljs-keyword">in</span> page[<span class="hljs-string">'Children'</span>]:
                ou_ids.append(ou[<span class="hljs-string">'Id'</span>])
                ou_ids.extend(get_ou_ids(org, ou[<span class="hljs-string">'Id'</span>]))
    <span class="hljs-keyword">except</span> ClientError <span class="hljs-keyword">as</span> err:
        logger.error(err.response[<span class="hljs-string">'Error'</span>][<span class="hljs-string">'Message'</span>])
        <span class="hljs-keyword">raise</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> ou_ids

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">list_accounts</span>():</span>
    org = boto3.client(<span class="hljs-string">'organizations'</span>)
    root_id = <span class="hljs-string">'r-xxxx'</span>
    ou_id_list = [root_id]
    ou_id_list.extend(get_ou_ids(org, root_id))
    accounts = []

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">for</span> ou_id <span class="hljs-keyword">in</span> ou_id_list:
            paginator = org.get_paginator(<span class="hljs-string">'list_accounts_for_parent'</span>)
            page_iterator = paginator.paginate(ParentId=ou_id)
            <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> page_iterator:
                <span class="hljs-keyword">for</span> account <span class="hljs-keyword">in</span> page[<span class="hljs-string">'Accounts'</span>]:
                    item = [
                        account[<span class="hljs-string">'Id'</span>],
                        account[<span class="hljs-string">'Name'</span>],
                    ]
                    accounts.append(item)
    <span class="hljs-keyword">except</span> ClientError <span class="hljs-keyword">as</span> err:
        logger.error(err.response[<span class="hljs-string">'Error'</span>][<span class="hljs-string">'Message'</span>])
        <span class="hljs-keyword">raise</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> accounts

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_cost_json</span>(<span class="hljs-params">start, end</span>):</span>
    ce = boto3.client(<span class="hljs-string">'ce'</span>)
    response = ce.get_cost_and_usage(
        TimePeriod={
            <span class="hljs-string">'Start'</span>: start,
            <span class="hljs-string">'End'</span> :  end,
        },
        Granularity=<span class="hljs-string">'DAILY'</span>,
        Metrics=[
            <span class="hljs-string">'NetUnblendedCost'</span>
        ],
        GroupBy=[
            {
                <span class="hljs-string">'Type'</span>: <span class="hljs-string">'DIMENSION'</span>,
                <span class="hljs-string">'Key'</span>: <span class="hljs-string">'LINKED_ACCOUNT'</span>
            }
        ]
    )
    <span class="hljs-keyword">return</span> response[<span class="hljs-string">'ResultsByTime'</span>]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    today = datetime.date.today()
    start = today.replace(day=<span class="hljs-number">1</span>).strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
    end = today.strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
    key = <span class="hljs-string">'daily-cost-'</span> + today.strftime(<span class="hljs-string">'%Y-%m'</span>) + <span class="hljs-string">'.csv'</span>
    output_file = <span class="hljs-string">'/tmp/output.csv'</span>
    bucket = os.environ[<span class="hljs-string">'BUCKET'</span>]
    account_list = pandas.DataFrame(list_accounts(), columns=[<span class="hljs-string">'Account Id'</span>, <span class="hljs-string">'Account Name'</span>])
    daily_cost_list = get_cost_json(start, end)

    merged_cost = pandas.DataFrame(
        index=[],
        columns=[<span class="hljs-string">'Account Id'</span>]
    )

    <span class="hljs-keyword">for</span> index, item <span class="hljs-keyword">in</span> enumerate(daily_cost_list):
        normalized_json = pandas.json_normalize(item[<span class="hljs-string">'Groups'</span>])
        split_keys = pandas.DataFrame(
            normalized_json[<span class="hljs-string">'Keys'</span>].tolist(),
            columns=[<span class="hljs-string">'Account Id'</span>]
        )
        cost = pandas.concat(
            [split_keys, normalized_json[<span class="hljs-string">'Metrics.NetUnblendedCost.Amount'</span>]],
            axis=<span class="hljs-number">1</span>
        )
        renamed_cost = cost.rename(
            columns={<span class="hljs-string">'Metrics.NetUnblendedCost.Amount'</span>: item[<span class="hljs-string">'TimePeriod'</span>][<span class="hljs-string">'Start'</span>]}
        )
        merged_cost = pandas.merge(merged_cost, renamed_cost, on=<span class="hljs-string">'Account Id'</span>, how=<span class="hljs-string">'outer'</span>)

    daily_cost = pandas.merge(account_list, merged_cost, on=<span class="hljs-string">'Account Id'</span>, how=<span class="hljs-string">'right'</span>)
    daily_cost.to_csv(output_file, index=<span class="hljs-literal">False</span>)
    upload_s3(output_file, key, bucket)
</code></pre>
<p>Now all that is left is setting an arbitrary startup schedule in EventBridge and a Lambda function as the target.</p>
]]></content:encoded></item><item><title><![CDATA[Configure actions-runner-controller with proxy in private EKS cluster]]></title><description><![CDATA[Goals of this post

Run actions-runner-controller on Private EKS cluster
Communication with GitHub API is through on-premises proxy server

This post is validated with the following configuration.

Amazon EKS: Kubernetes 1.22
actions-runner-controlle...]]></description><link>https://hayao-k.dev/configure-actions-runner-controller-with-proxy-in-private-eks-cluster</link><guid isPermaLink="true">https://hayao-k.dev/configure-actions-runner-controller-with-proxy-in-private-eks-cluster</guid><category><![CDATA[AWS]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[EKS]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[proxy]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Sun, 12 Jun 2022 17:12:31 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-goals-of-this-post">Goals of this post</h2>
<ul>
<li>Run actions-runner-controller on Private EKS cluster</li>
<li>Communication with GitHub API is through on-premises proxy server</li>
</ul>
<p>This post is validated with the following configuration.</p>
<ul>
<li>Amazon EKS: Kubernetes 1.22</li>
<li>actions-runner-controller: v0.23.0</li>
<li>cert-manager v1.8.0</li>
<li>eksctl: 0.93.0</li>
<li>kubectl: v1.21.2</li>
<li>AWS CLI: 2.5.7</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655015672397/ooCYIo2d1.png" alt="image.png" /></p>
<h2 id="heading-what-is-actions-runner-controller">What is actions-runner-controller?</h2>
<p>Controller for operating GitHub Actions self-hosted runners on Kubernetes cluster.</p>
<p>https://github.com/actions-runner-controller/actions-runner-controller</p>
<p>The following describes the flow of building actions-runner-controller on private EKS cluster.</p>
<h2 id="heading-create-private-eks-cluster">Create private EKS Cluster</h2>
<p>Set up kubectl, eksctl, and AWS CLI in your working environment in advance. See below for detailed instructions.</p>
<p><strong>kubectl:</strong> https://kubernetes.io/docs/tasks/tools/</p>
<p><strong>eksctl:</strong> https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html</p>
<p><strong>AWS CLI:</strong> https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html</p>
<p>Please refer to the following document to create a private cluster with eksctl.</p>
<p>https://eksctl.io/usage/eks-private-cluster/</p>
<p>In this case, created an EKS cluster using the following config file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">eksctl.io/v1alpha5</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterConfig</span>

<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">actions-runner</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">ap-northeast-1</span>
  <span class="hljs-attr">version:</span> <span class="hljs-string">"1.22"</span>

<span class="hljs-attr">privateCluster:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">skipEndpointCreation:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">vpc:</span>
  <span class="hljs-attr">subnets:</span>
    <span class="hljs-attr">private:</span>
      <span class="hljs-attr">ap-northeast-1a:</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-xxxxxxxxxxxxxxxxx</span>
      <span class="hljs-attr">ap-northeast-1c:</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-yyyyyyyyyyyyyyyyy</span>
      <span class="hljs-attr">ap-northeast-1d:</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">subnet-zzzzzzzzzzzzzzzzz</span>
</code></pre>
<p>For a private EKS cluster, the following VPC endpoints are required.</p>
<ul>
<li>ECR (<code>ecr.api</code>, <code>ecr.dkr</code>)</li>
<li>S3 (Gateway)</li>
<li>EC2 </li>
<li>STS </li>
<li>CloudWatch Logs</li>
</ul>
<p>If you enable a private cluster with eksctl (<code>privateCluster.enabled</code>), these endpoints will be created automatically. </p>
<p>In this case, I had a requirement to use a VPC already connected via Direct Connect, so skip endpoint creation with <code>privateCluster.skipEndpointCreation</code> to use the existing VPC and endpoints.</p>
<p><strong>NOTE:</strong> if you specify an existing VPC, eksctl will edit the route table.</p>
<p>However, if the subnet is associated with the main route table, eksctl will not edit the route table and will fail to create the cluster. Therefore, it is necessary to create and associate a route table explicitly.</p>
<p>eksctl communicates with the AWS API via a proxy server.  However, communication to private EKS cluster endpoints does not use a proxy server, so the following environment variable should be set.</p>
<pre><code><span class="hljs-keyword">export</span> https_proxy=http:<span class="hljs-comment">//xxx.xxx.xxx.xxx:yyyy</span>
<span class="hljs-keyword">export</span> no_proxy=.eks.amazonaws.com
</code></pre><p>The following command creates a cluster. Creating a private cluster takes longer than creating a regular cluster. This configuration took about 22 minutes, but if you are creating VPC or VPC endpoints, it will take even longer, so adjust the timeout value.</p>
<pre><code>$ eksctl create cluster <span class="hljs-operator">-</span><span class="hljs-operator">-</span>timeout 30m <span class="hljs-operator">-</span>f <span class="hljs-keyword">private</span><span class="hljs-operator">-</span>cluster.yaml
</code></pre><h2 id="heading-register-managed-node-groups">Register Managed Node Groups</h2>
<p>Add UserData to the launch template used by the managed node group and configure the Docker daemon to use a proxy.</p>
<p>This template is based on the following blog.</p>
<p>https://yomon.hatenablog.com/entry/2020/10/eks_docker_inside_proxy#%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%89%E5%9E%8B%E3%83%8E%E3%83%BC%E3%83%89%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%ABProxy%E8%A8%AD%E5%AE%9A</p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">"2010-09-09"</span>
<span class="hljs-attr">Parameters:</span>
  <span class="hljs-attr">ClusterIp:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>
  <span class="hljs-attr">ProxyIP:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>
  <span class="hljs-attr">ProxyPort:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">Number</span>
  <span class="hljs-attr">SshKeyPairName:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">String</span>

<span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">EKSManagedNodeInPrivateNetworkLaunchTemplate:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::EC2::LaunchTemplate</span>
    <span class="hljs-attr">Properties:</span> 
      <span class="hljs-attr">LaunchTemplateName:</span> <span class="hljs-string">eks-managednodes-in-private-network</span>
      <span class="hljs-attr">LaunchTemplateData:</span> 
        <span class="hljs-attr">InstanceType:</span> <span class="hljs-string">t3.small</span>
        <span class="hljs-attr">KeyName:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">SshKeyPairName</span>
        <span class="hljs-attr">TagSpecifications:</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ResourceType:</span> <span class="hljs-string">instance</span>
            <span class="hljs-attr">Tags:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">Name</span> 
                <span class="hljs-attr">Value:</span> <span class="hljs-string">actions-runner-nodegroup</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">ResourceType:</span> <span class="hljs-string">volume</span>
            <span class="hljs-attr">Tags:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">Key:</span> <span class="hljs-string">Name</span>
                <span class="hljs-attr">Value:</span> <span class="hljs-string">actions-runner-nodegroup</span>

        <span class="hljs-attr">UserData:</span> <span class="hljs-type">!Base64</span> 
          <span class="hljs-attr">"Fn::Sub":</span> <span class="hljs-string">|
            Content-Type: multipart/mixed; boundary="==BOUNDARY=="
            MIME-Version:  1.0
</span>
            <span class="hljs-string">--==BOUNDARY==</span>
            <span class="hljs-attr">Content-Type:</span> <span class="hljs-string">text/cloud-boothook;</span> <span class="hljs-string">charset="us-ascii"</span>

            <span class="hljs-comment">#Set the proxy hostname and port</span>
            <span class="hljs-string">PROXY=${ProxyIP}:${ProxyPort}</span>
            <span class="hljs-string">MAC=$(curl</span> <span class="hljs-string">-s</span> <span class="hljs-string">http://169.254.169.254/latest/meta-data/mac/)</span>
            <span class="hljs-string">VPC_CIDR=$(curl</span> <span class="hljs-string">-s</span> <span class="hljs-string">http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MAC/vpc-ipv4-cidr-blocks</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">|</span> <span class="hljs-string">tr</span> <span class="hljs-string">' '</span> <span class="hljs-string">','</span><span class="hljs-string">)</span>

            <span class="hljs-comment">#Create the docker systemd directory</span>
            <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">/etc/systemd/system/docker.service.d</span>

            <span class="hljs-comment">#Configure docker with the proxy</span>
            <span class="hljs-string">cloud-init-per</span> <span class="hljs-string">instance</span> <span class="hljs-string">docker_proxy_config</span> <span class="hljs-string">tee</span> <span class="hljs-string">&lt;&lt;EOF</span> <span class="hljs-string">/etc/systemd/system/docker.service.d/http-proxy.conf</span> <span class="hljs-string">&gt;/dev/null</span>
            [<span class="hljs-string">Service</span>]
            <span class="hljs-string">Environment="HTTP_PROXY=http://$PROXY"</span>
            <span class="hljs-string">Environment="HTTPS_PROXY=http://$PROXY"</span>
            <span class="hljs-string">Environment="NO_PROXY=${ClusterIp},$VPC_CIDR,localhost,127.0.0.1,169.254.169.254,.internal,s3.amazonaws.com,.s3.ap-northeast-1.amazonaws.com,api.ecr.ap-northeast-1.amazonaws.com,dkr.ecr.ap-northeast-1.amazonaws.com,ec2.ap-northeast-1.amazonaws.com,ap-northeast-1.eks.amazonaws.com"</span>
            <span class="hljs-string">EOF</span>

            <span class="hljs-comment">#Reload the daemon and restart docker to reflect proxy configuration at launch of instance</span>
            <span class="hljs-string">cloud-init-per</span> <span class="hljs-string">instance</span> <span class="hljs-string">reload_daemon</span> <span class="hljs-string">systemctl</span> <span class="hljs-string">daemon-reload</span> 
            <span class="hljs-string">cloud-init-per</span> <span class="hljs-string">instance</span> <span class="hljs-string">enable_docker</span> <span class="hljs-string">systemctl</span> <span class="hljs-string">enable</span> <span class="hljs-string">--now</span> <span class="hljs-string">--no-block</span> <span class="hljs-string">docker</span>
            <span class="hljs-string">--==BOUNDARY==</span>
</code></pre>
<p>After creating the launch template, create a Managed Node Group with the following config file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">eksctl.io/v1alpha5</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ClusterConfig</span>

<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">actions-runner</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">ap-northeast-1</span>

<span class="hljs-attr">managedNodeGroups:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">t3s-proxy</span>
  <span class="hljs-attr">desiredCapacity:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">privateNetworking:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">launchTemplate:</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">lt-xxxxxxxxxxxxxxxxx</span>
    <span class="hljs-attr">version:</span> <span class="hljs-string">"1"</span>
</code></pre>
<p>Run <code>create nodegroup</code>.</p>
<pre><code>$ eksctl <span class="hljs-keyword">create</span> nodegroup -f nodegroup.yaml
</code></pre><h2 id="heading-installing-actions-runner-controller">Installing actions-runner-controller</h2>
<p>actions-runner-controller uses cert-manager to manage certificates for Admission Webhooks, so it must be installed in advance.</p>
<pre><code>$ kubectl apply -f <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/github.com/cert</span>-manager/cert-manager/releases/download/v1.<span class="hljs-number">8.0</span>/cert-manager.yaml
</code></pre><p>To use actions-runner-controller in a proxy environment, it is necessary to configure the proxy server information in the following three locations.</p>
<ol>
<li>manager container of controller-manager</li>
<li>runner container of RunnerDeployment</li>
<li>sidecar (dind) container of RunnerDeployment</li>
</ol>
<p>I deployed actions-runner-controller with kubectl this time.</p>
<p><code>1</code> sets the https_proxy environment variable to the Deployment definition of controller-manager.
<code>2</code> and <code>3</code> set the https_proxy environment variable to the definition of RunnerDeployment.</p>
<p>Edit the manifest file of actions-runner-controller as follows.</p>
<pre><code class="lang-diff">33836     spec:
33837       containers:
33838       - args:
33839         - --metrics-addr=127.0.0.1:8080
33840         - --enable-leader-election
33841         command:
33842         - /manager
31843         env:
31844         - name: GITHUB_TOKEN
31845           valueFrom:
31846             secretKeyRef:
31847               key: github_token
31848               name: controller-manager
31849               optional: true
31850         - name: GITHUB_APP_ID
31851           valueFrom:
31852             secretKeyRef:
31853               key: github_app_id
31854               name: controller-manager
31855               optional: true
31856         - name: GITHUB_APP_INSTALLATION_ID
31857           valueFrom:
31858             secretKeyRef:
31859               key: github_app_installation_id
31860               name: controller-manager
31861               optional: true
31862         - name: GITHUB_APP_PRIVATE_KEY
31863           value: /etc/actions-runner-controller/github_app_private_key
<span class="hljs-addition">+ 31864       - name: http_proxy</span>
<span class="hljs-addition">+ 31865         value: "http://xxx.xxx.xxx.xxx:xxxx"</span>
<span class="hljs-addition">+ 31866       - name: https_proxy</span>
<span class="hljs-addition">+ 31867         value: "http://xxx.xxx.xxx.xxx:xxxx"</span>
<span class="hljs-addition">+ 31868       - name: no_proxy</span>
<span class="hljs-addition">+ 31869         value: "172.20.0.1,*.eks.amazonaws.com"</span>
31870         image: summerwind/actions-runner-controller:v0.22.3
31871         name: manager
</code></pre>
<p>Run <code>kubectl create</code>.</p>
<pre><code>$ kubectl create <span class="hljs-operator">-</span>f actions<span class="hljs-operator">-</span>runners<span class="hljs-operator">-</span>controller.yaml
</code></pre><p><strong>NOTE:</strong> Due to a known issue with Kubernetes, we must use <code>create</code> (or <code>replace</code> when updating) instead of <code>apply</code>.</p>
<p>https://github.com/actions-runner-controller/actions-runner-controller/issues/1317</p>
<p>GitHub API can be authenticated using the GitHub App or Personal Access Token (PAT).</p>
<p>In this case, we use PAT authentication. Set the appropriate scope for the token depending on the type of runner and create a secret.</p>
<pre><code>kubectl create secret generic controller<span class="hljs-operator">-</span>manager \
    <span class="hljs-operator">-</span>n actions<span class="hljs-operator">-</span>runner<span class="hljs-operator">-</span>system \
    <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">from</span><span class="hljs-operator">-</span>literal<span class="hljs-operator">=</span>github_token<span class="hljs-operator">=</span>${GITHUB_TOKEN}
</code></pre><p><strong>NOTE:</strong> PAT must be pre-authorized before accessing an Organization with SAML SSO configured.</p>
<p>https://docs.github.com/ja/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on</p>
<p>RunnerDeployment is defined below.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">actions.summerwind.dev/v1alpha1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">RunnerDeployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">runner-deploy</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">enterprise:</span> <span class="hljs-string">&lt;enterprise-name&gt;</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">runner-container</span>
      <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">RUNNER_FEATURE_FLAG_EPHEMERAL</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"true"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"http://xxx.xxx.xxx.xxx:yyyy"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">https_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"http://xxx.xxx.xxx.xxx:yyyy"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">no_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"172.20.0.1,10.1.2.0/24,*.eks.amazonaws.com"</span>
      <span class="hljs-attr">dockerEnv:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">http_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"http://xxx.xxx.xxx.xxx:yyyy"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">https_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"http://xxx.xxx.xxx.xxx:yyyy"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">no_proxy</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">"172.20.0.1,10.1.2.0/24,*.eks.amazonaws.com"</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">actions.summerwind.dev/v1alpha1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">HorizontalRunnerAutoscaler</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">runner-deployment-autoscaler</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">scaleDownDelaySecondsAfterScaleOut:</span> <span class="hljs-number">300</span>
  <span class="hljs-attr">scaleTargetRef:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">runner-deploy</span>
  <span class="hljs-attr">minReplicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">maxReplicas:</span> <span class="hljs-number">5</span>
  <span class="hljs-attr">metrics:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">PercentageRunnersBusy</span>
    <span class="hljs-attr">scaleUpThreshold:</span> <span class="hljs-string">'0.75'</span>
    <span class="hljs-attr">scaleDownThreshold:</span> <span class="hljs-string">'0.25'</span>
    <span class="hljs-attr">scaleUpFactor:</span> <span class="hljs-string">'2'</span>
    <span class="hljs-attr">scaleDownFactor:</span> <span class="hljs-string">'0.5'</span>
</code></pre>
<p>When a container is used in a GitHub Actions job, the docker container for dind, launched as a RunnerDeployment sidecar, is used. Specify dockerEnv so that this dind container can perform image pulls.</p>
<p>After deployment, confirm that the pod is successfully started and is visible from GitHub.</p>
<pre><code>$ kubectl apply <span class="hljs-operator">-</span>f RunnerDeployment.yaml

$ kubectl get pod
NAME                            READY   STATUS    RESTARTS   AGE
pod<span class="hljs-operator">/</span>runner<span class="hljs-operator">-</span>deploy<span class="hljs-operator">-</span>xxxxx<span class="hljs-operator">-</span>xxxxx   <span class="hljs-number">2</span><span class="hljs-operator">/</span><span class="hljs-number">2</span>     Running   <span class="hljs-number">0</span>          53s
</code></pre><p>If controller-manager does not start properly, check the settings in <code>1</code> or GitHub; if Runner does not start properly, check the settings in <code>2</code> or <code>3</code>.</p>
]]></content:encoded></item><item><title><![CDATA[Differences between Amazon ECR and Inspector image scanning capabilities]]></title><description><![CDATA[Introduction
At AWS re:Invent 2021, the vulnerability management service Amazon Inspector was redesigned and released as the all-new Amazon Inspector (v2). The new Inspector not only scans EC2 but also scans container images stored in Amazon ECR.
Imp...]]></description><link>https://hayao-k.dev/differences-between-amazon-ecr-and-inspector-image-scanning-capabilities</link><guid isPermaLink="true">https://hayao-k.dev/differences-between-amazon-ecr-and-inspector-image-scanning-capabilities</guid><category><![CDATA[AWS]]></category><category><![CDATA[containers]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Thu, 27 Jan 2022 14:26:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/tjX_sniNzgQ/upload/v1643261400548/r9vQldns7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>At AWS re:Invent 2021, the vulnerability management service Amazon Inspector was redesigned and released as the all-new Amazon Inspector (v2). The new Inspector not only scans EC2 but also scans container images stored in Amazon ECR.</p>
<p><strong>Improved, Automated Vulnerability Management for Cloud Workloads with a New Amazon Inspector</strong>
https://aws.amazon.com/jp/blogs/aws/improved-automated-vulnerability-management-for-cloud-workloads-with-a-new-amazon-inspector/</p>
<h2 id="heading-differences">Differences</h2>
<p>With the introduction of image scanning by Inspector, the ECR scanning function is now called Basic scanning, while the Inspector scanning function is called Enhanced scanning. The main differences between Basic scanning and Enhanced scanning are as follows.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Basic scanning</td><td>Enhanced scanning</td></tr>
</thead>
<tbody>
<tr>
<td>Vulnerability Detection Target</td><td>OS packages only</td><td>OS and programming language packages</td></tr>
<tr>
<td>Vulnerability Detection Timing</td><td>When the image is pushed</td><td>When vulnerabilities occur</td></tr>
<tr>
<td>Pricing</td><td>Free</td><td>Not free</td></tr>
<tr>
<td>Amazon EventBrdige Integration</td><td>Yes (Scan result summary only)</td><td>yes</td></tr>
<tr>
<td>AWS Security Hub Integration</td><td>No</td><td>Yes</td></tr>
<tr>
<td>AWS Organizations Integration</td><td>No</td><td>Yes</td></tr>
</tbody>
</table>
</div><h3 id="heading-vulnerability-detection-target">Vulnerability Detection Target</h3>
<p>Basic scanning provides a scan using the CVE database of the open-source Clair project. Only OS packages are targeted for vulnerability detection.</p>
<p>Enhanced scanning is capable of detecting vulnerabilities in programming language packages in addition to OS packages. Supported programming languages are as follows.</p>
<ul>
<li>C#</li>
<li>Golang</li>
<li>Java</li>
<li>JavaScript</li>
<li>PHP</li>
<li>Python</li>
<li>Ruby</li>
<li>Rust</li>
</ul>
<p><strong>Supported operating systems and programming languages</strong>
https://docs.aws.amazon.com/inspector/latest/user/supported.html</p>
<h3 id="heading-vulnerability-detection-timing">Vulnerability Detection Timing</h3>
<p>Basic scanning can be triggered when an image is pushed (Scan on push) or manually. Manual scans are limited to once every 24 hours for each image.</p>
<p>For Enhanced scanning, continuous scans can be used for repositories. Continuous scanning automatically scans whenever an image is pushed and whenever the Amazon Inspector vulnerability database is updated. This means that vulnerabilities can be detected at about the same time as vulnerability information is updated.</p>
<p>For Enhanced scanning, you can define whether you want to enable continuous scanning or only scan on push in the repository name scan filter. Manual scan execution is not possible with Enhanced scanning.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643262223990/O0VnIJyA-.png" alt="image.png" /></p>
<h3 id="heading-pricing">Pricing</h3>
<p>Basic scanning is free of charge, but Enhanced scanning is a paid feature, with the following monthly fees for the Tokyo region as of 12/2021</p>
<ul>
<li>Per first container image scanned during a push to ECR: $0.11</li>
<li>Number of Continuous scans for a container image: $0.01 per scan</li>
</ul>
<p>Pricing page: https://aws.amazon.com/inspector/pricing/</p>
<h3 id="heading-integration-with-amazon-eventbridge">Integration with Amazon EventBridge</h3>
<p>When Basic scanning is complete, an event is sent to EventBridge, and you can get a summary of the scan results.</p>
<p><strong>Amazon ECR events and EventBridge</strong><br />https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr-eventbridge.html</p>
<p>Enhanced scanning is enabled, the following events are sent to EventBridge.</p>
<ul>
<li>Event for a repository scan frequency change</li>
<li>Event for an initial image scan (equivalent to a Basic scanning)</li>
<li>Event for an image scan finding update (created, updated, closed) </li>
</ul>
<p>Enhanced scanning differs from Basic scanning in that an event is issued each time a vulnerability is found. For more details, please refer to the following document.</p>
<p><strong>Enhanced scanning</strong><br />https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning-enhanced.html#image-scanning-enhanced-events</p>
<h3 id="heading-integration-with-aws-security-hub">Integration with AWS Security Hub</h3>
<p>In environments where AWS Security Hub is enabled, integration with Amazon Inspector is also automatically enabled. Vulnerabilities discovered by Enhanced scanning are automatically sent to AWS Security Hub and can be included in existing security operations workflows.</p>
<p><strong>Integration with AWS Security Hub</strong><br />https://docs.aws.amazon.com/inspector/latest/user/securityhub-integration.html</p>
<h3 id="heading-aws-organizations-support">AWS Organizations support</h3>
<p>New Amazon Inspector also supports integration with AWS Organizations. Delegated administrator accounts can enable EC2 scans and ECR scans (Enhanced scanning) for all member accounts in an organization to manage vulnerabilities. It also supports the automatic activation of new accounts added to the organization.</p>
<p><strong>Enabling scans for member accounts</strong><br />https://docs.aws.amazon.com/inspector/latest/user/adding-member-accounts.html</p>
<h2 id="heading-points-to-note-when-using-enhanced-scanning">Points to note when using Enhanced scanning</h2>
<h3 id="heading-manually-scanning-cannot-be-performed">Manually scanning cannot be performed</h3>
<p>Manually scanning cannot be performed in an environment with Enhanced scanning enabled.</p>
<h3 id="heading-per-repository-scan-settings-deprecated">Per-repository scan settings deprecated</h3>
<p>Setting up a repository-level image scan has been deprecated. The use of scan filters is recommended even when using Basic scanning. The scan filter settings will take precedence if the repository-level and the registry scan filters are set. Continuous Scan setting for Enhanced Scanning can only be specified in the scan filter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643265515196/iWL-wqc7I.png" alt="image.png" /></p>
<h3 id="heading-continuous-scanning-is-available-up-to-30-days-after-the-image-is-pushed">Continuous scanning is available up to 30 days after the image is pushed.</h3>
<p>When a continuous scan is configured, the image will be scanned for 30 days after being pushed to the repository. If the image has not been updated in the last 30 days, the continuous scan for that image will be paused.</p>
<h3 id="heading-basic-scanning-cannot-be-used-together">Basic scanning cannot be used together</h3>
<p>The scan settings are for the entire registry. It is not possible to switch the scan type for each repository. Also, if you enable Enhanced scanning, you will not see the results of previous Basic scanning in the console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273279763/KvagriZ9mn.png" alt="image.png" /></p>
<p>The results are not lost, and you can refer to them again by changing the scan type back to Basic scanning.</p>
<h2 id="heading-try-enhanced-scanning">Try Enhanced scanning</h2>
<h3 id="heading-settings">Settings</h3>
<p>From the Private registry in the Amazon ECR console, click Edit for Scanning.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273468452/beZAMBm1B.png" alt="image.png" /></p>
<p>Select Enhanced scanning as the scan type. Note that this is a setting for the registry, so it cannot be used in conjunction with the basic scan. For both continuous scan and scan on push, you can use scan filters to narrow down the repositories to be scanned. In this example, we set the filter to target repository names starting with test/, but you can also target all repositories.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273602676/22PqR5uMg.png" alt="image.png" /></p>
<p>Click Confirm when you see a message about additional charges for Enhanced scanning.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273670850/ofdoYBqa2J.png" alt="image.png" /></p>
<p>Make sure that the scan settings have changed from Basic to Enhanced version.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273773662/KLyugvvfI.png" alt="image.png" /></p>
<p>If you check the Account management page of the new Inspector console, you will see that ECR container scanning is Enabled.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643273825663/4ZeOn_B0y.png" alt="image.png" /></p>
<p>You can also check the coverage of repositories that have Enhanced scanning enabled in the Inspector dashboard. Four repositories in this environment had "scan on push" enabled in Basic scanning, so it has been carried over. Since the per-repository image scan setting has been deprecated, it is preferable to disable and use scan filters to manage the coverage.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643274977315/8rROpeINY-.png" alt="image.png" /></p>
<h3 id="heading-operation-check">Operation check</h3>
<p>Let's push the image. Since Enhanced scanning can detect language-specific vulnerabilities, I used a container image of a Java application. Enter the repository name to match the scan filter you have just set.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643275111607/AbsqNENH-.png" alt="image.png" /></p>
<p>You will see that the scan frequency for the created repository is set to Continuous.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643275157623/uzj19Jgzb.png" alt="image.png" /></p>
<p>I pushed an image containing an old Java application for testing to detect the vulnerability.</p>
<pre><code class="lang-shell">$ aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

$ docker tag test/java-sample-app:v1.1.0 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test/java-sample-app:v1.1.0
$ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test/java-sample-app:v1.1.0
The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test/java-sample-app]
c5baabd61e59: Pushed 
88e64033fc7f: Pushed 
0466be121ce3: Pushed 
b87942114db6: Pushed 
v1.1.0: digest: sha256:69a58fe6b25d21015da0f170ffa6934f2ec2827562238ecbde902ldkgi2d082b size: 1165
</code></pre>
<p>If you check the Inspector console, you will see that it detects quite many vulnerabilities.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643275275589/ljSJCQuEe.png" alt="image.png" /></p>
<p>Let's look at one of the critical vulnerabilities from All Findings. We are detecting a vulnerability in Jackson, a library for processing JSON in Java. Basic scanning does not detect vulnerabilities in programming language packages such as this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643275443774/dt8ILsUha.png" alt="image.png" /></p>
<p>The results were also linked to Security Hub.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1643275482845/YVtKPHGQx.png" alt="image.png" /></p>
<h3 id="heading-enhanced-scanning-be-performed-on-an-existing-image">Enhanced scanning be performed on an existing image?</h3>
<p>When I enabled Enhanced Scanning, I noticed that the initial scan is also performed on some images stored in the existing repository. As far as I can tell, the initial scan will be performed on the stored images if the following conditions are met.</p>
<ul>
<li>The repository is the target of a continuous scan</li>
<li>The image has been pushed within 30 days</li>
</ul>
<h2 id="heading-official-documents">Official Documents</h2>
<p><strong>Image scanning</strong><br />https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html</p>
<p><strong>Scanning Amazon ECR container images with Amazon Inspector</strong><br />https://docs.aws.amazon.com/inspector/latest/user/enable-disable-scanning-ecr.html</p>
]]></content:encoded></item><item><title><![CDATA[AWS PrivateLink supports Amazon S3! What makes you happy?]]></title><description><![CDATA[Introduction
With the update on 2/2/2021, you can now access Amazon S3 via AWS PrivateLink.
🚀 AWS PrivateLink for Amazon S3 is Now Generally Available
The following is a description of the advantages and points to note compared to the existing Gatew...]]></description><link>https://hayao-k.dev/aws-privatelink-supports-amazon-s3-what-makes-you-happy</link><guid isPermaLink="true">https://hayao-k.dev/aws-privatelink-supports-amazon-s3-what-makes-you-happy</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[S3]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Fri, 12 Feb 2021 18:21:26 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>With the update on 2/2/2021, you can now access Amazon S3 via AWS PrivateLink.</p>
<p>🚀 <a target="_blank" href="https://aws.amazon.com/jp/blogs/aws/aws-privatelink-for-amazon-s3-now-available/">AWS PrivateLink for Amazon S3 is Now Generally Available</a></p>
<p>The following is a description of the advantages and points to note compared to the existing Gateway-type VPC endpoint.</p>
<h2 id="good-points">Good points</h2>
<h3 id="access-from-on-premises">Access from on-premises</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613153270363/qhrnY5DCgA.png" alt="image.png" />
I think this is what we have been waiting for.  </p>
<p>In the case of the gateway endpoint, the S3 endpoint remains a global IP address.  The route table configuration ensures that communication to S3 in the VPC is directed to the VPC endpoint. </p>
<p>Due to this specification, it was not possible to directly communicate with S3 from an on-premises environment connected using AWS Direct Connect or VPN.</p>
<p>How did we handle it so far?<br />It was necessary to build a proxy server on EC2. Naturally, there are operating and maintenance costs associated with this proxy server.</p>
<p>For Interface type endpoints with AWS PrivateLink, an ENI is created in the VPC. The private IP assigned to the ENI can be used to make a private connection directly from the on-premises environment to S3!</p>
<h3 id="access-from-another-vpc-or-region">Access from another VPC or region</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613062028425/2es_gXRtS.png" alt="image.png" /></p>
<p>The concept is the same as on-premise.<br />Since it can be accessed via the private IP of the VPC, it can also be accessed by VPCs in other regions connected via VPC Peering.</p>
<p><strong>It does not mean</strong> that a single endpoint can access S3 buckets in multiple regions. 
As with the Gateway VPC endpoint, the S3 buckets to be accessed must reside in the same region. VPCs and VPC endpoints in the Tokyo region can only access S3 buckets that reside in the Tokyo region.</p>
<h3 id="use-gateway-and-interface-endpoints-together">Use Gateway and Interface endpoints together</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613153213183/Dj0uk6POX.png" alt="image.png" />
S3 Gateway endpoint and Interface endpoint can use together. For example, an application on a VPC can continue to use the Gateway endpoint, while an application on-premises can use the Interface endpoint.</p>
<p>As discussed in more detail below, this configuration has advantages because Interface endpoints must take into account data transfer charges and changes to the endpoint URL.</p>
<h2 id="points-to-consider">Points to consider</h2>
<h3 id="usage-fee">Usage fee</h3>
<p>Gateway VPC endpoints are free of charge, but Interface VPC endpoints are charged per endpoint (USD/hour) and per GB of processed data .</p>
<p>Pricing in Tokyo Region</p>
<table>
<thead>
<tr>
<td>Pricing per VPC endpoint per AZ ($/hour)</td><td>Pricing per GB data processed ($)</td></tr>
</thead>
<tbody>
<tr>
<td>$ 0.014</td><td>$0.01</td></tr>
</tbody>
</table>
<p>If you have a redundant configuration with 3 AZs, you will have to pay for 3 endpoints.<br />Pricing per GB data processed is also not very expensive, but if you are exchanging large amounts of data, you may need to consider the price beforehand.</p>
<h3 id="specifying-endpoints">Specifying endpoints</h3>
<p>The S3 Interface endpoint does not support the private DNS feature. The checkbox is grayed out when creating the endpoint.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613153657632/ZycKUzSPv.png" alt="image.png" />
The Private DNS feature resolves the default DNS name of the service (e.g. ec2.ap-northeast-1.amazonaws.com) to the private IP address assigned to the VPC endpoint.</p>
<p>This allows VPCs using Route 53 Resolver (formerly Amazon Provided DNS) and on-premises environments using Route 53 Resolver for Hybrid Clouds to use the default DNS hostname to send requests to VPC endpoints.</p>
<p>Since this feature is not available, you will need to access S3 using your VPC endpoint's unique DNS name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613153839662/KbQ7MLO9y.png" alt="image.png" /></p>
<p>AWS CLI Example</p>
<pre><code class="lang-shell-session">$ aws s3 ls s3://my-bucket/ --endpoint-url https://bucket.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com
</code></pre>
<p>AWS SDK (boto3) example</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> boto3

s3_client = boto3.client(
    <span class="hljs-string">'s3'</span>,
    endpoint_url = <span class="hljs-string">'https://bucket.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com'</span>
)
</code></pre>
<p>You need to change the subdomain depending on the API you want to operate. Specify ”bucket" for the subdomain if you want to perform API operations related to the S3 bucket. It's a "bucket", not a bucket name.</p>
<table>
<thead>
<tr>
<td>API operation</td><td>Endpoint example</td></tr>
</thead>
<tbody>
<tr>
<td>Bucket</td><td>bucket.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com</td></tr>
<tr>
<td>Access Point</td><td>accesspoint.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com</td></tr>
<tr>
<td>S3 Control</td><td>control.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com</td></tr>
</tbody>
</table>
<p>For example, if you directly specify the private IP of the endpoint, <code>SSL validation failed</code> will occur.</p>
<pre><code class="lang-shell">$ aws s3 ls --endpoint-url https://10.0.0.9
SSL validation failed for https://10.0.0.9/ ("hostname 'xxx.xxx.xxx.xxx' doesn't match either of 's3.ap-northeast-1.amazonaws.com', 
'*.accesspoint.vpce-1a2b3c4d-5e6f-ap-northeast-1d.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.control.vpce-1a2b3c4d-5e6f-ap-northeast-1a.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.accesspoint.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com', 'bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1a.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.s3-control.ap-northeast-1.amazonaws.com', '*.control.vpce-1a2b3c4d-5e6f-ap-northeast-1d.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.s3.ap-northeast-1.amazonaws.com', '*.s3-accesspoint.ap-northeast-1.amazonaws.com', '*.control.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com', '*.bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1d.s3.ap-northeast-1.vpce.amazonaws.com', 
'bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1c.s3.ap-northeast-1.vpce.amazonaws.com', 'bucket.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com',
'*.control.vpce-1a2b3c4d-5e6f-ap-northeast-1c.s3.ap-northeast-1.vpce.amazonaws.com', '*.bucket.vpce-1a2b3c4d-5e6f.s3.ap-northeast-1.vpce.amazonaws.com', '*.accesspoint.vpce-1a2b3c4d-5e6f-ap-northeast-1c.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1c.s3.ap-northeast-1.vpce.amazonaws.com', 
'bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1d.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.accesspoint.vpce-1a2b3c4d-5e6f-ap-northeast-1a.s3.ap-northeast-1.vpce.amazonaws.com', 
'*.bucket.vpce-1a2b3c4d-5e6f-ap-northeast-1a.s3.ap-northeast-1.vpce.amazonaws.com'",)
</code></pre>
<h2 id="experiment">Experiment</h2>
<h3 id="what-if-my-application-doesnt-support-endpoint-url-changes">What if my application doesn't support endpoint URL changes?</h3>
<p>I tried setting the private IP address of the interface VPC endpoint locally to the hosts file to force it to override the default DNS name locally.  Then, I was able to perform the bucket-level operations. However, this kind of usage is not documented and will most likely not be supported by AWS.</p>
<pre><code class="lang-shell">10.0.0.9 s3.ap-northeast-1.amazonaws.com
10.0.0.9 &lt;bucket-name&gt;.s3.ap-northeast-1.amazonaws.com
</code></pre>
<pre><code class="lang-shell">$ aws s3 ls
$ aws s3 ls s3://my-bucket/
</code></pre>
<p>Please note that there are many disadvantages to using a hosts file, so please use it only as a reference.</p>
<p>e.g.</p>
<ul>
<li>No redundant configuration</li>
<li>Need to add a DNS name for each bucket.</li>
</ul>
<h2 id="official-document">Official Document</h2>
<p><strong>Amazon S3 and interface VPC endpoints (AWS PrivateLink)</strong>
https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html</p>
<p>That's all.
Happy storing!</p>
]]></content:encoded></item><item><title><![CDATA[Integration of AWS Security Hub and kube-bench]]></title><description><![CDATA[Introduction
kube-bench has been added to the 3rd party integrations in AWS Security Hub.
🚀 AWS Security Hub adds open source tool integrations with Kube-bench and Cloud Custodian
https://aws.amazon.com/jp/about-aws/whats-new/2020/12/aws-security-hu...]]></description><link>https://hayao-k.dev/integration-of-aws-security-hub-and-kube-bench</link><guid isPermaLink="true">https://hayao-k.dev/integration-of-aws-security-hub-and-kube-bench</guid><category><![CDATA[AWS]]></category><category><![CDATA[Security]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Wed, 06 Jan 2021 16:06:35 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>kube-bench has been added to the 3rd party integrations in AWS Security Hub.</p>
<p>🚀 <strong>AWS Security Hub adds open source tool integrations with Kube-bench and Cloud Custodian</strong></p>
<p>https://aws.amazon.com/jp/about-aws/whats-new/2020/12/aws-security-hub-adds-open-source-tool-integration-with-kube-bench-and-cloud-custodian/</p>
<p>With this integration, the results of the CIS Kubernetes Benchmark and CIS Amazon EKS Benchmark checks run by kube-bench can now be centrally managed in AWS Security Hub.</p>
<h3 id="what-is-cis-benchmark">What is CIS Benchmark?</h3>
<p>CIS Benchmark is a set of guidelines published by the Center for Internet Security (CIS), a non-profit organization in the United States, for strengthening various operating systems, servers, and cloud environments.</p>
<p>CIS Benchmark is referenced in compliance requirements such as the PCI DSS when it states "industry-accepted system hardening standards".</p>
<p>You can download over 140 CIS Benchmarks in PDF format from the CIS website.
https://www.cisecurity.org/cis-benchmarks/</p>
<h3 id="what-is-kube-bench">What is kube-bench?</h3>
<p><a target="_blank" href="https://github.com/aquasecurity/kube-bench">kube-bench</a> is a Go application that allows you to check if your environment complies with the recommendations listed in the CIS Kubernetes Benchmark.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/aquasecurity/kube-bench">https://github.com/aquasecurity/kube-bench</a></div>
<p>kube-bench supports checking not only the CIS Kubernetes Benchmark, but also the CIS Amazon Elastic Kubernetes Service (EKS) Benchmark and CIS Google Kubernetes Engine (GKE) Benchmark. </p>
<h3 id="what-is-aws-security-hub">What is AWS Security Hub?</h3>
<p><a target="_blank" href="https://aws.amazon.com/jp/security-hub/">AWS Security Hub</a> is a service for aggregating and centrally managing various security data for the entire AWS environment.</p>
<p>Security Hub integrates with many 3rd party security products, as well as AWS services such as Amazon GuardDuty, Inspector, and Macie.<br />Data can be sent from Security Hub-enabled products to the Security Hub and vice versa.</p>
<p>Internally, the result information is managed in a JSON type format called <a target="_blank" href="https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-findings-format.html">AWS Security Finding Format (ASFF)</a>, so you can import your own data as long as it conforms to this format. </p>
<h2 id="prerequisites">Prerequisites</h2>
<p>I have confirmed that the following versions work.</p>
<ul>
<li>Amazon EKS: 1.17</li>
<li>eksctl: 0.30.0</li>
<li>kube-bench: 0.40.0</li>
</ul>
<p>Ran the CIS Amazon EKS Benchmark v1.0 check in an EKS cluster.</p>
<h2 id="enable-integration">Enable integration</h2>
<p>Search for kube-bench from the Security Hub console integration and click "<strong>Accept findings</strong>" to see information about the IAM policies required to send the findings to Security Hub.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609947958134/8vMGmCzWE.png" alt="image.png" /></p>
<p>Select "Accept findings" again on the confirmation screen, and the status will be activated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609948088131/LiPQjSLYG.png" alt="image.png" /></p>
<h2 id="configue-iam-roles">Configue IAM Roles</h2>
<p>In order for kube-bench to run in an EKS cluster, the pod must have permissions to send check results to Security Hub.</p>
<p>There are two ways to assign access to AWS resources to a Pod.</p>
<ul>
<li>Use <a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html">IAM Roles for Service Accounts (IRSA)</a></li>
<li>Use IAM roles configured for node groups</li>
</ul>
<p>Using IRSA, you can associate an IAM role with a Kubernetes service account.<br />This allows you to provide Security Hub access only to pods launched by kube-bench.</p>
<p>Note that if you use an IAM role that is configured for a node group, all Pods running under that group will be assigned access to the Security Hub.</p>
<p>This time we will use IRSA.
If you haven't created an IAM OIDC Provider to use IRSA, do so first!</p>
<pre><code class="lang-shell-session">$ eksctl utils associate-iam-oidc-provider \
--cluster {CLUSTER_NAME} --approve --region ap-northeast-1
</code></pre>
<p>Next, create an IAM role with the following policies attached.<br />Replace the regions as needed.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"securityhub:BatchImportFindings"</span>,
            <span class="hljs-attr">"Resource"</span>: [
                <span class="hljs-string">"arn:aws:securityhub:ap-northeast-1::product/aqua-security/kube-bench"</span>
            ]
        }
    ]
}
</code></pre>
<p>Add the following policy to the trust relationship of the IAM role. 
 Set <code>&lt;ACCOUNT_ID&gt;</code> and <code>&lt;OIDR_PROVIDER_ID&gt;</code> to your own.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
  <span class="hljs-attr">"Statement"</span>: [
    {
      <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
      <span class="hljs-attr">"Principal"</span>: {
        <span class="hljs-attr">"Federated"</span>: <span class="hljs-string">"arn:aws:iam::&lt;ACCOUNT_ID&gt;:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/&lt;OIDC_PROVIDER_ID&gt;"</span>
      },
      <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"sts:AssumeRoleWithWebIdentity"</span>,
      <span class="hljs-attr">"Condition"</span>: {
        <span class="hljs-attr">"StringLike"</span>: {
          <span class="hljs-attr">"oidc.eks.ap-northeast-1.amazonaws.com/id/&lt;OIDC_PROVIDER_ID&gt;:sub"</span>: <span class="hljs-string">"system:serviceaccount:kube-bench:*"</span>,
          <span class="hljs-attr">"oidc.eks.ap-northeast-1.amazonaws.com/id/&lt;OIDC_PROVIDER_ID&gt;:aud"</span>: <span class="hljs-string">"sts.amazonaws.com"</span>
        }
      }
    }
  ]
}
</code></pre>
<p>This is written assuming that <code>kube-bech</code> is used for Namespace.</p>
<h2 id="create-container-image">Create container image</h2>
<p>We need to build a container image of kube-bench and push it to ECR.<br />First, let's create an ECR repository to store the image.</p>
<pre><code class="lang-shell-session">$ aws ecr create-repository --repository-name k8s/kube-bench --image-tag-mutability MUTABLE
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/k8s/kube-bench",
        "registryId": "123456789012",
        "repositoryName": "k8s/kube-bench",
        "repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench",
        "createdAt": 1607747704.0,
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}
</code></pre>
<p>Clone the source code of kube-bench from GitHub.</p>
<pre><code class="lang-shell-session">$ git clone https://github.com/aquasecurity/kube-bench.git
Cloning into 'kube-bench'...
remote: Enumerating objects: 4115, done.
remote: Total 4115 (delta 0), reused 0 (delta 0), pack-reused 4115
Receiving objects: 100% (4115/4115), 7.69 MiB | 5.28 MiB/s, done.
Resolving deltas: 100% (2644/2644), done.

$ cd kube-bench
</code></pre>
<p>If you want to send the execution result to Security Hub, you need to edit <code>cfg/eks-1.0/config.yaml</code> and rewrite the AWS account, region and cluster name to be executed before building the image.</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">AWS_ACCOUNT:</span> <span class="hljs-string">"123456789012"</span>
<span class="hljs-attr">AWS_REGION:</span> <span class="hljs-string">"ap-northeast-1"</span>
<span class="hljs-attr">CLUSTER_ARN:</span> <span class="hljs-string">"arn:aws:eks:ap-northeast-1:123456789012:cluster/{YOUR_CLUSTER_NAME}"</span>
</code></pre>
<p>Build the container image and push it to ECR.</p>
<pre><code class="lang-shell-session">$ aws ecr get-login-password | docker login --username AWS --password-stdin https://123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

$ docker build -t k8s/kube-bench .
...
Successfully built 6ad073f96455
Successfully tagged k8s/kube-bench:latest

$ docker tag k8s/kube-bench:latest 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench:latest
$ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench:latest
The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/k8s/kube-bench]
bd6c279efeaa: Pushed 
3032a8c3bf7a: Pushed 
b0efa7564210: Pushed 
fe4cf80d4f2c: Pushed 
31c3d3db74eb: Pushed 
d2e36eff2b5d: Pushed 
f4666769fca7: Pushed 
latest: digest: sha256:da95de0edccad7adb6fd6c80137a65b5458142efe14efa94ac44cc5c6ce6b2ef size: 1782
</code></pre>
<h2 id="run-kube-bench">Run kube-bench</h2>
<h3 id="creating-a-service-account">Creating a Service Account</h3>
<p>Create a service account for kube-bench using a manifest file like the following. Note that the annotations specifies the IAM role that we just created.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ServiceAccount</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">kube-bench-sa</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">kube-bench</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">eks.amazonaws.com/role-arn:</span> <span class="hljs-string">arn:aws:iam::123456789012:role/&lt;IAM_ROLE_NAME&gt;</span>
</code></pre>
<p>Create a namespace <code>kube-bench</code> and apply it.</p>
<pre><code class="lang-shell-session">$ kubectl create ns kube-bench
kubectl create ns kube-bench

$ kubectl apply -f sa.yaml
serviceaccount/kube-bench-sa created

$ kubectl get sa -n kube-bench
NAME            SECRETS   AGE
default         1         6m32s
kube-bench-sa   1         11s
</code></pre>
<h3 id="run-a-job">Run a job</h3>
<p>Open <code>jobs-eks.yaml</code> cloned from GitHub, and edit <code>image</code> and <code>command</code>. Adding the <code>--asff</code> flag to the command will send the results to Security Hub.   </p>
<p>Add the service account name that you just created.<br />The edited file should look like the following.</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">batch/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Job</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">kube-bench</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">hostPID:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">kube-bench-sa</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">kube-bench</span>
          <span class="hljs-attr">image:</span> <span class="hljs-number">123456789012.</span><span class="hljs-string">dkr.ecr.region.amazonaws.com/k8s/kube-bench:latest</span>
          <span class="hljs-attr">command:</span> [<span class="hljs-string">"kube-bench"</span>, <span class="hljs-string">"node"</span>, <span class="hljs-string">"--benchmark"</span>, <span class="hljs-string">"eks-1.0"</span>, <span class="hljs-string">"--asff"</span>]
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">var-lib-kubelet</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/var/lib/kubelet</span>
              <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">etc-systemd</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/etc/systemd</span>
              <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">etc-kubernetes</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/etc/kubernetes</span>
              <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">restartPolicy:</span> <span class="hljs-string">Never</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">var-lib-kubelet</span>
          <span class="hljs-attr">hostPath:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">"/var/lib/kubelet"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">etc-systemd</span>
          <span class="hljs-attr">hostPath:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">"/etc/systemd"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">etc-kubernetes</span>
          <span class="hljs-attr">hostPath:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">"/etc/kubernetes"</span>
</code></pre>
<p>Run kube-bench as a Kubernetes job and make sure it exits successfully.</p>
<pre><code class="lang-shell-session">$ kubectl apply -f job-eks.yaml -n kube-bench
job.batch/kube-bench created

$ kubectl get all -n kube-bench                                                                                                                                                    
NAME                   READY   STATUS      RESTARTS   AGE
pod/kube-bench-q4sbd   0/1     Completed   0          55s

NAME                   COMPLETIONS   DURATION   AGE
job.batch/kube-bench   1/1           5s         55s
</code></pre>
<p>If the following message is output to the Pod's log and the job fails, please check whether the contents of <code>cfg/eks-1.0/config.yaml</code> are correct.</p>
<pre><code class="lang-shell-session">failed to output to ASFF: finding publish failed: MissingEndpoint: 'Endpoint' configuration is required for this service
</code></pre>
<h2 id="check-the-findings-of-security-hub">Check the findings of Security Hub</h2>
<p>Normally, the results of running kube-bench will be output to the Pod's log.</p>
<pre><code class="lang-shell-session">$ kubectl logs -f pod/kube-bench-2j2ss -n kube-bench
[INFO] 3 Worker Node Security Configuration
[INFO] 3.1 Worker Node Configuration Files
[PASS] 3.1.1 Ensure that the proxy kubeconfig file permissions are set to 644 or more restrictive (Scored)
[PASS] 3.1.2 Ensure that the proxy kubeconfig file ownership is set to root:root (Scored)
[PASS] 3.1.3 Ensure that the kubelet configuration file has permissions set to 644 or more restrictive (Scored)
[PASS] 3.1.4 Ensure that the kubelet configuration file ownership is set to root:root (Scored)
[INFO] 3.2 Kubelet
[PASS] 3.2.1 Ensure that the --anonymous-auth argument is set to false (Scored)
[PASS] 3.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow (Scored)
[PASS] 3.2.3 Ensure that the --client-ca-file argument is set as appropriate (Scored)
[PASS] 3.2.4 Ensure that the --read-only-port argument is set to 0 (Scored)
[PASS] 3.2.5 Ensure that the --streaming-connection-idle-timeout argument is not set to 0 (Scored)
[PASS] 3.2.6 Ensure that the --protect-kernel-defaults argument is set to true (Scored)
[PASS] 3.2.7 Ensure that the --make-iptables-util-chains argument is set to true (Scored) 
[PASS] 3.2.8 Ensure that the --hostname-override argument is not set (Scored)
[WARN] 3.2.9 Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture (Scored)
[PASS] 3.2.10 Ensure that the --rotate-certificates argument is not set to false (Scored)
[PASS] 3.2.11 Ensure that the RotateKubeletServerCertificate argument is set to true (Scored)

== Remediations node ==
3.2.9 If using a Kubelet config file, edit the file to set eventRecordQPS: to an appropriate level.
If using command line arguments, edit the kubelet service file
/etc/systemd/system/kubelet.service on each worker node and
set the below parameter in KUBELET_SYSTEM_PODS_ARGS variable.
Based on your system, restart the kubelet service. For example:
systemctl daemon-reload
systemctl restart kubelet.service


== Summary node ==
14 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO

== Summary total ==
14 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO
</code></pre>
<p>If you specify the <code>--asff</code> flag and send the results to Security Hub, only the number of cases imported to Security Hub will be listed in the Pod's log.</p>
<pre><code class="lang-shell-session">$ kubectl logs -f pod/kube-bench-q4sbd -n kube-bench
2020/12/12 14:51:07 Number of findings that were successfully imported:1
</code></pre>
<p>In the case of this environment, the result was one.</p>
<p>Note that the results will be sent to Security Hub only for items whose check result is <code>FAIL</code> or <code>WARN</code>. If all checks are PASSed, no results will be generated to Security Hub.</p>
<p>If you check the findings in Security Hub console, you will see that the one result sent was successfully captured.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609865063675/HVb45n5vE.png" alt="image.png" /></p>
<h2 id="using-custom-insights">Using Custom Insights</h2>
<p>Security Hub has a feature called Custom Insights that allows you to aggregate data by resource or environment using specific grouping condition and filters. </p>
<p>Custom Insights can be created in the Insights section of the Security Hub console by simply setting any grouping condition, filters</p>
<p>For example, if you are using Security Hub in a multi-account configuration, you can use the management account to aggregate the detection results for each member account.</p>
<p>Let's check out an example of a custom insight with the grouping condition set to AWS account ID and the product name <code>Kube-bench</code> set as a filter.<br />By clicking on the account ID, you will be able to instantly see the kube-bench results for the target account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609865405180/GltC8xAHL.png" alt="image.png" /></p>
<p>Even if you are operating with a single account, you can use it to list the detection results for each EKS cluster by setting the resource ID as a group by condition.</p>
<h2 id="references">References</h2>
<p><strong>Integrating kube-bench with AWS Security Hub</strong><br />https://github.com/aquasecurity/kube-bench/blob/master/docs/asff.md</p>
<p><strong>Managing custom insights</strong><br />https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-custom-insights.html</p>
]]></content:encoded></item><item><title><![CDATA[Setting up a working environment for Amazon EKS with AWS CloudShell]]></title><description><![CDATA[Introduction
AWS Cloud Shell was announced at the Werner Vogels Keynote at AWS re:Invent 2020.
🚀 AWS CloudShell – Command-Line Access to AWS Resources
https://aws.amazon.com/jp/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/
AWS Cloud...]]></description><link>https://hayao-k.dev/setting-up-a-working-environment-for-amazon-eks-with-aws-cloudshell</link><guid isPermaLink="true">https://hayao-k.dev/setting-up-a-working-environment-for-amazon-eks-with-aws-cloudshell</guid><category><![CDATA[AWS]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Sun, 20 Dec 2020 15:54:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608447420210/fa1FRBWoL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>AWS Cloud Shell was announced at the Werner Vogels Keynote at AWS re:Invent 2020.</p>
<p>🚀 <strong>AWS CloudShell – Command-Line Access to AWS Resources</strong>
https://aws.amazon.com/jp/blogs/aws/aws-cloudshell-command-line-access-to-aws-resources/</p>
<p>AWS CloudShell is a browser-based shell that can be launched directly from the AWS management console.<br />The shell can use Bash, PowerShell, Z shell, and comes preconfigured with tools to support the AWS CLI and other major development languages.</p>
<p>The pre-setup tools are described in the following document.</p>
<p><strong>AWS CloudShell compute environment: specifications and software</strong>
https://docs.aws.amazon.com/cloudshell/latest/userguide/vm-specs.html</p>
<p>For example, kubectl is not installed.<br />Let's prepare the working environment for Amazon EKS by yourself.</p>
<p>Is the installation of additional software in a shell environment supported?<br />Yes, but it must be managed by the user. (Shared Responsibility model😎)</p>
<h2 id="setting-up">Setting up</h2>
<p>Just click on the icon on the managed console to launch CloudShell.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608447948046/qp6GDByP-.png" alt="image.png" /></p>
<ul>
<li>I installed what I could think of for now.  </li>
<li>Please change the version as needed.</li>
<li>In this article, not discuss how to link clusters and IAM users/roles.</li>
<li>If you want Docker, use Cloud9!</li>
</ul>
<p>The installation directory is set to <code>$HOME/.local/bin</code><br />This is because the persistent storage that is maintained between sessions is <code>$HOME</code>. (See the second half of this article for details.)</p>
<pre><code><span class="hljs-comment"># Create directory</span>
mkdir -p <span class="hljs-variable">$HOME</span>/.<span class="hljs-built_in">local</span>/bin
<span class="hljs-built_in">cd</span> <span class="hljs-variable">$HOME</span>/.<span class="hljs-built_in">local</span>/bin

<span class="hljs-comment"># kubectl</span>
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.13/bin/linux/amd64/kubectl
chmod +x kubectl

<span class="hljs-comment"># Create $HOME/.kube/config</span>
aws eks update-kubeconfig --name &lt;YOUR_CLUSTER_NAME&gt;

<span class="hljs-comment"># eksctl</span>
curl --silent --location <span class="hljs-string">"https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_<span class="hljs-subst">$(uname -s)</span>_amd64.tar.gz"</span> | tar xz -C /tmp
sudo mv /tmp/eksctl <span class="hljs-variable">$HOME</span>/.<span class="hljs-built_in">local</span>/bin

<span class="hljs-comment"># helm</span>
<span class="hljs-built_in">export</span> VERIFY_CHECKSUM=<span class="hljs-literal">false</span>
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
sudo mv /usr/<span class="hljs-built_in">local</span>/bin/helm <span class="hljs-variable">$HOME</span>/.<span class="hljs-built_in">local</span>/bin
</code></pre><p>Packages installed by yum cannot be placed in persistent storage (<code>$HOME</code>), so they need to be installed for each new session.<br />You can write a command in your <code>.bash_profile</code> to automatically install them when you start CloudShell.</p>
<p>I want to use kubectl completion, so I installed bash-completion.</p>
<pre><code><span class="hljs-comment"># .bash_profile</span>

<span class="hljs-comment"># Get the aliases and functions</span>
<span class="hljs-keyword">if</span> [ -f ~/.bashrc ]; <span class="hljs-keyword">then</span>
        . ~/.bashrc
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># User specific environment and startup programs</span>

PATH=<span class="hljs-variable">$PATH</span>:<span class="hljs-variable">$HOME</span>/.<span class="hljs-built_in">local</span>/bin:<span class="hljs-variable">$HOME</span>/bin

<span class="hljs-built_in">export</span> PATH

<span class="hljs-comment"># Install at startup</span>
sudo yum install -y bash-completion &gt; /dev/null 2&gt;&amp;1
</code></pre><p>The kubectl completion configuration can be saved to persistent storage.</p>
<pre><code><span class="hljs-attribute">kubectl</span> completion bash &gt;  <span class="hljs-variable">$HOME</span>/.bash_completion
</code></pre><h2 id="notes-on-cloudshell">Notes on CloudShell</h2>
<h3 id="persistent-storage">Persistent storage</h3>
<ul>
<li>CloudShell can use 1 GB of persistent storage per region.    </li>
<li>Persistent storage is located in your home directory ($HOME) and is private.  (It is not shared among users.)  </li>
<li>Only this area is guaranteed to be retained between sessions.  </li>
<li>Software and other data stored in directories other than the home directory will not be retained at the end of a session. </li>
<li>The data in the persistent storage will be deleted after 120 days from the end of the last session.</li>
</ul>
<h3 id="cloudshell-access-permissions">CloudShell access permissions</h3>
<p>As with any service, you need to explicitly grant CloudShell access to the target IAM user/role.<br />It is easiest to use the AWSCloudShellFullAccess AWS managed policy, but if you want to restrict file upload/download via CloudShell, you can use a custom policy like the following.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [{
        <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"CloudShellUser"</span>,
        <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
        <span class="hljs-attr">"Action"</span>: [
            <span class="hljs-string">"cloudshell:*"</span>
        ],
        <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"*"</span>
    }, {
        <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"DenyUploadDownload"</span>,
        <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Deny"</span>,
        <span class="hljs-attr">"Action"</span>: [
            <span class="hljs-string">"cloudshell:GetFileDownloadUrls"</span>,
            <span class="hljs-string">"cloudshell:GetFileUploadUrls"</span>
        ],
        <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"*"</span>
    }]
}
</code></pre>
<h3 id="permissions-to-access-aws-services-from-within-cloudshell">Permissions to access AWS services from within CloudShell.</h3>
<p>Automatically uses the IAM credentials you used to sign in to the AWS Management Console.<br />This means that the operating IAM user/role must have explicit permission to access the target AWS service.</p>
<h2 id="reference">Reference</h2>
<p><strong>AWS CloudShell - User Guide</strong><br />https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html</p>
]]></content:encoded></item><item><title><![CDATA[Let's Try Lambda Container Support with SAM CLI]]></title><description><![CDATA[Introduction
Container image support for AWS Lambda was announced at AWS re:Invent 2020.
🚀 AWS Lambda now supports container images as a packaging formathttps://aws.amazon.com/about-aws/whats-new/2020/12/aws-lambda-now-supports-container-images-as-a...]]></description><link>https://hayao-k.dev/lets-try-lambda-container-support-with-sam-cli</link><guid isPermaLink="true">https://hayao-k.dev/lets-try-lambda-container-support-with-sam-cli</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Wed, 09 Dec 2020 13:18:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607437550850/CZBTYIxRD.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Container image support for AWS Lambda was announced at AWS re:Invent 2020.</p>
<p>🚀 <strong>AWS Lambda now supports container images as a packaging format</strong><br />https://aws.amazon.com/about-aws/whats-new/2020/12/aws-lambda-now-supports-container-images-as-a-packaging-format/</p>
<p>You might have thought that AWS had finally released a service like Cloud Run. 
Unfortunately, it doesn't offer the ability to easily run any container image with Lambda.</p>
<p>This is a feature that allows you to package and deploy Lambda functions as container images. Therefore, the container image you create must implement the Lambda Runtime API and be compatible with Lambda functions.</p>
<p>The AWS SAM CLI  supports container images in v1.13.1.</p>
<p>🚀 <strong>Release 1.13.1 - Support for Lambda Container Images</strong><br />https://github.com/aws/aws-sam-cli/releases/tag/v1.13.1</p>
<h2 id="lets-try">Let's Try</h2>
<p>As mentioned earlier, AWS SAM CLI must be v1.13.1 or higher.</p>
<pre><code class="lang-shell">$ sam --version
SAM CLI, version 1.13.1
</code></pre>
<h3 id="create-project">Create project</h3>
<p>You can create a project from a template with <code>sam init</code> command.<br />For package type questions, select <code>2 - image</code>.<br />I have created a sample project using the base image from nodejs12.x.</p>
<pre><code class="lang-shell">$ sam init
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1
What package type would you like to use?
        1 - Zip (artifact is a zip uploaded to S3)
        2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2

Which base image would you like to use?
        1 - amazon/nodejs12.x-base
        2 - amazon/nodejs10.x-base
        3 - amazon/python3.8-base
        4 - amazon/python3.7-base
        5 - amazon/python3.6-base
        6 - amazon/python2.7-base
        7 - amazon/ruby2.7-base
        8 - amazon/ruby2.5-base
        9 - amazon/go1.x-base
        10 - amazon/java11-base
        11 - amazon/java8.al2-base
        12 - amazon/java8-base
        13 - amazon/dotnetcore3.1-base
        14 - amazon/dotnetcore2.1-base
Base image: 1

Project name [sam-app]:

Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Base Image: amazon/nodejs12.x-base
    Dependency Manager: npm
    Output Directory: .

    Next steps can be found in the README file at ./sam-app/README.md
</code></pre>
<p>The generated file is as follows
You will see that Dockerfile has been created.</p>
<pre><code class="lang-shell">$ cd sam-app
$ tree
.
├── events
│   └── event.json
├── hello-world
│   ├── app.js
│   ├── Dockerfile
│   ├── package.json
│   └── tests
│       └── unit
│           └── test-handler.js
├── README.md
└── template.yaml
</code></pre>
<p>The base image of Dockerfile is specified as an image provided by AWS.</p>
<pre><code class="lang-Dockerfile">FROM public.ecr.aws/lambda/nodejs:12

COPY app.js package.json ./

RUN npm install

# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambdaHandler"]
</code></pre>
<p>The base image provided by AWS is preloaded with the runtime and other components needed to execute Lambda functions, so you just need to add the Lambda function code and its dependencies.</p>
<p>If you want to make your own base image Lambda-compatible, you need to add <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/runtimes-images.html#runtimes-api-client">Runtime Interface Clients (RIC)</a>, a set of software packages that implement the Lambda Runtime API, to your base image.</p>
<p>Let' take a look at the contents of template.yaml.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">'2010-09-09'</span>
<span class="hljs-attr">Transform:</span> <span class="hljs-string">AWS::Serverless-2016-10-31</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">&gt;
  sam-app
</span>
  <span class="hljs-string">Sample</span> <span class="hljs-string">SAM</span> <span class="hljs-string">Template</span> <span class="hljs-string">for</span> <span class="hljs-string">sam-app</span>

<span class="hljs-comment"># More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst</span>
<span class="hljs-attr">Globals:</span>
  <span class="hljs-attr">Function:</span>
    <span class="hljs-attr">Timeout:</span> <span class="hljs-number">3</span>

<span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">HelloWorldFunction:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::Serverless::Function</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">PackageType:</span> <span class="hljs-string">Image</span>
      <span class="hljs-comment"># ImageConfig:</span>
        <span class="hljs-comment"># Uncomment this to override command here from the Dockerfile</span>
        <span class="hljs-comment"># Command: ["app.lambdaHandler"]</span>
      <span class="hljs-attr">Events:</span>
        <span class="hljs-attr">HelloWorld:</span>
          <span class="hljs-attr">Type:</span> <span class="hljs-string">Api</span>
          <span class="hljs-attr">Properties:</span>
            <span class="hljs-attr">Path:</span> <span class="hljs-string">/hello</span>
            <span class="hljs-attr">Method:</span> <span class="hljs-string">get</span>
    <span class="hljs-attr">Metadata:</span>
      <span class="hljs-attr">DockerTag:</span> <span class="hljs-string">nodejs12.x-v1</span>
      <span class="hljs-attr">DockerContext:</span> <span class="hljs-string">./hello-world</span>
      <span class="hljs-attr">Dockerfile:</span> <span class="hljs-string">Dockerfile</span>

<span class="hljs-attr">Outputs:</span>
  <span class="hljs-comment"># ServerlessRestApi is an implicit API created out of Events key under Serverless::Function</span>
  <span class="hljs-comment"># Find out more about other implicit resources you can reference within SAM</span>
  <span class="hljs-comment"># https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api</span>
  <span class="hljs-attr">HelloWorldApi:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">"API Gateway endpoint URL for Prod stage for Hello World function"</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"</span>
  <span class="hljs-attr">HelloWorldFunction:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">"Hello World Lambda Function ARN"</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">HelloWorldFunction.Arn</span>
  <span class="hljs-attr">HelloWorldFunctionIamRole:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">"Implicit IAM Role created for Hello World function"</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">HelloWorldFunctionRole.Arn</span>
</code></pre>
<p>In order to package with a container image, <code>PackageType: Image</code> must be specified.<br />https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-packagetype</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">PackageType:</span> <span class="hljs-string">Image</span>
</code></pre>
<p>To build a container image, use the Metadata resource attribute to declare information such as Dockerfile, Context, Tag, etc.<br />You can also use the DockerBuildArgs entry to specify the arguments for the build execution.
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-build.html#build-container-image</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">Metadata:</span>
      <span class="hljs-attr">DockerTag:</span> <span class="hljs-string">nodejs12.x-v1</span>
      <span class="hljs-attr">DockerContext:</span> <span class="hljs-string">./hello-world</span>
      <span class="hljs-attr">Dockerfile:</span> <span class="hljs-string">Dockerfile</span>
</code></pre>
<h3 id="build">Build</h3>
<p>Build the container image with the <code>sam build</code> command.</p>
<pre><code class="lang-shell">$ sam build
Building codeuri: . runtime: None metadata: {'DockerTag': 'nodejs12.x-v1', 'DockerContext': './hello-world', 'Dockerfile': 'Dockerfile'} functions: ['HelloWorldFunction']
Building image for HelloWorldFunction function
Setting DockerBuildArgs: {} for HelloWorldFunction function
Step 1/4 : FROM public.ecr.aws/lambda/nodejs:12
 ---&gt; ccbddaf00c51
Step 2/4 : COPY app.js package.json ./
 ---&gt; fb16c5342f63
Step 3/4 : RUN npm install
 ---&gt; Running in faa9eb4d503c
npm WARN deprecated debug@3.2.6: Debug versions &gt;=3.2.0 &lt;3.2.7 || &gt;=4 &lt;4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm notice created a lockfile as package-lock.json. You should commit this file.
added 107 packages from 544 contributors and audited 107 packages in 5.755s

16 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

 ---&gt; 8db6f5505058
Step 4/4 : CMD ["app.lambdaHandler"]
 ---&gt; Running in 08e14cc877aa
 ---&gt; 271c7f34a0c7
Successfully built 271c7f34a0c7
Successfully tagged helloworldfunction:nodejs12.x-v1

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
</code></pre>
<p>A packaged container image was created.</p>
<pre><code class="lang-shell">$ docker image ls
REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZE
helloworldfunction             nodejs12.x-v1       271c7f34a0c7        3 minutes ago       471MB
</code></pre>
<h3 id="test">Test</h3>
<p>To test locally the Lambda functions packaged as container images, you can use the Lambda Runtime Interface Emulator (RIE), which is a lightweight web server.</p>
<p>AWS-provided base image has a built-in RIE, so you can simply run the container from the built image and run the test　immediately.</p>
<pre><code class="lang-shell">$ docker run -d -p 9000:8080 helloworldfunction:nodejs12.x-v1
a510b1f8bf0fd5c0d7c6a3716ddaf96462452abcdff26285d1e8471c6e829cc8

$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
{"statusCode":200,"body":"{\"message\":\"hello world\"}"}
</code></pre>
<p>Since RIE is responsible for converting HTTP requests into JSON events and proxying them, it does not support X-Ray or other Lambda integrations.</p>
<p>If you use your own base image, you can run tests by adding the RIE to the image or by launching a container with a bind-mounted RIE as Entrypoint.<br />https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-alternative</p>
<p>Of course, since we are using the SAM CLI this time, you can also run tests with <code>sam local invoke</code>.</p>
<pre><code class="lang-shell">$ sam local invoke
Invoking Container created from helloworldfunction:nodejs12.x-v1
Image was not found.
Building image.................
Skip pulling image and use local one: helloworldfunction:rapid-1.13.1.

START RequestId: 05284faf-5a20-46fc-a695-111eb3d0f085 Version: $LATEST
END RequestId: 05284faf-5a20-46fc-a695-111eb3d0f085
REPORT RequestId: 05284faf-5a20-46fc-a695-111eb3d0f085  Init Duration: 1.86 ms  Duration: 96.09 ms      Billed Duration: 100 ms Memory Size: 128 MB     Max Memory Used: 128 MB
</code></pre>
<h3 id="deploy">Deploy</h3>
<p>A repository must be created in advance to push the container image, but you can leave the image pushing to the SAM CLI!</p>
<pre><code class="lang-shell">$ aws ecr create-repository --repository-name lambda-container-test
{
    "repository": {
        "repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "registryId": "123456789012",
        "imageTagMutability": "MUTABLE",
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/lambda-container-test",
        "repositoryName": "lambda-container-test",
        "createdAt": 1606926983.0
    }
}
</code></pre>
<p>Execute the <code>sam deploy</code> command.<br />By specifying the repository that you have just created in the <code>Image Repository</code>, the image will be pushed automatically during deployment. </p>
<pre><code class="lang-shell">$ sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]:
        AWS Region [us-east-1]: ap-northeast-1
        Image Repository []: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test
        Images that will be pushed:
          helloworldfunction:nodejs12.x-v1 to 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test:helloworldfunction-271c7f34a0c7-nodejs12.x-v1

        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: n
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: y
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: y
        SAM configuration file [samconfig.toml]:
        SAM configuration environment [default]:

        Looking for resources needed for deployment: Found!

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx
                A different default S3 bucket can be set in samconfig.toml

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test]
6d7acece320f: Pushed
105893862807: Pushed
3642f26c4fcb: Pushed
1807102b87b6: Pushed
120614c3628c: Pushed
0d8c48ae73f7: Pushed
e4f26f8be15f: Pushed
af6d16f2417e: Pushed
helloworldfunction-271c7f34a0c7-nodejs12.x-v1: digest: sha256:7a83998f07e54249948db91846abd9dbfecbe175181fe0876b5980a635b9e70f size: 1998


        Deploying with following values
        ===============================
        Stack name                   : sam-app
        Region                       : ap-northeast-1
        Confirm changeset            : False
        Deployment image repository  : 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-container-test
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {}
        Signing Profiles           : {}
--omitted--
</code></pre>
<p>Make a request to the created API Gateway and see how it works.</p>
<pre><code class="lang-shell">$ curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message":"hello world"}
</code></pre>
<p>It works!<br />You can see in the Lambda console that the function code has been deployed as a container image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607520314448/qc3e5p5lm.png" alt="image.png" /></p>
<h2 id="points-of-note">Points of note</h2>
<ul>
<li><p>Even if you remove the stack, the image that was pushed to the ECR remains. (It will not be deleted automatically)</p>
</li>
<li><p>I haven't made an exact comparison yet, but it seems to take longer to initialize compared to a ZIP deployment package.</p>
</li>
<li><p>The initialization time is included in the billing duration. (Perhaps because it is treated the same as a custom runtime.)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607518257540/ZH7g8r9qZ.png" alt="image.png" /></p>
<p><strong>Building a custom runtime</strong><br />https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-build</p>
<blockquote>
<p>Initialization counts towards billed execution time and timeout. </p>
</blockquote>
<h2 id="reference">Reference</h2>
<p><strong>New for AWS Lambda – Container Image Support</strong><br />https://aws.amazon.com/jp/blogs/aws/new-for-aws-lambda-container-image-support/</p>
<p><strong>AWS Lambda - Developer Guide</strong><br />https://docs.aws.amazon.com/lambda/latest/dg/lambda-images.html</p>
<p><strong>AWS Serverless Application Model - Developer Guide</strong><br />https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-build.html</p>
<p>I hope this article will help you.</p>
]]></content:encoded></item><item><title><![CDATA[Hashnodeの紹介 - 個人ブログの長所を兼ね備えた開発者コミュニティ]]></title><description><![CDATA[Hashnode とは
無料で利用可能な開発者向けのコンテンツ (ブログ) 作成プラットフォームおよびコミュニティです。※海外のサイトなので、投稿されているコンテンツは英語がメインです。

https://hashnode.com/ の top ページより
Hashnode は様々な機能をもっているのですが、プラットフォームとしての特徴は
Hashnode Co-Founder の Product Hunt への書き込みが表していると思いますので
以下に google 翻訳の上、引用させていただ...]]></description><link>https://hayao-k.dev/hashnode-introduction</link><guid isPermaLink="true">https://hayao-k.dev/hashnode-introduction</guid><category><![CDATA[Hashnode]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Mon, 07 Dec 2020 16:26:41 GMT</pubDate><content:encoded><![CDATA[<h2 id="hashnode">Hashnode とは</h2>
<p>無料で利用可能な開発者向けのコンテンツ (ブログ) 作成プラットフォームおよびコミュニティです。<br />※海外のサイトなので、投稿されているコンテンツは英語がメインです。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607358018847/78HTSReqF.png" alt="image.png" /></p>
<p>https://hashnode.com/ の top ページより</p>
<p>Hashnode は様々な機能をもっているのですが、プラットフォームとしての特徴は
Hashnode Co-Founder の Product Hunt への<a target="_blank" href="https://www.producthunt.com/posts/hashnode-platform">書き込み</a>が表していると思いますので
以下に google 翻訳の上、引用させていただきます。</p>
<blockquote>
<p>従来のパブリッシングプラットフォームは、コンテンツの所有権を犠牲にして可視性と
エンゲージメントを提供することに気づきました。
一方、セルフホストソリューションを使用する場合、記事は適切な可視性と到達範囲を獲得できません。
Hashnodeは、両方の長所を兼ね備えています。カスタムドメインをマッピングし、
独自のブランドで記事を公開し、組み込みの開発者コミュニティに配布することができます。</p>
</blockquote>
<p>投稿したコンテンツの所有権は完全にユーザーにあります。<br />一方でプラットフォームとしてコミュニティ機能を備えていますので<br />フォローしているユーザーやトレンドの記事を簡単に追うことができますし<br />自分の記事の共有や SEO といった観点でもメリットがあります。</p>
<h2 id="5qmf6io944gu5lia6yoo44ks57s55lul">機能の一部を紹介</h2>
<p>Hashnode を実際に利用してみて、お気に入りの機能をいくつか紹介させてください。</p>
<h3 id="markdown">Markdown エディタ</h3>
<p>これは外せませんね！</p>
<h3 id="44kr44k544k44og44oj44oh44kk44oz">カスタムドメイン</h3>
<p>Hashnode の大きな特徴として、カスタムドメインを簡単に設定することができます。<br />アカウント作成後に払い出される URL は <code>https://&lt;ユーザー名&gt;.hashnode.dev/</code> の形式です。<br />カスタムドメインの設定は Hashnode のダッシュボード上で、ドメインを指定し、<br />利用している DNS プロバイダに指定された CNAME レコードを追加するだけです。<br />デフォルトで HTTPS 化され、初回アクセス時に自動で Let's Encrypt の証明書が設定されます。</p>
<p>私のアカウントの例では以下のようになります。</p>
<ul>
<li>デフォルト:  https://hayao-k.hashnode.dev/</li>
<li>カスタムドメイン: https://hayao-k.dev/</li>
</ul>
<p>たまたま「.dev」ドメインを利用しているため、短縮しただけのように見えてしまいますが<br />ご自身が所有するドメインを設定し、ブログを運用することができます。</p>
<p>※ ルート (Zone Apex) をカスタムドメインに指定するには利用する DNS プロバイダが<br />CNAME Flattening をサポートしている必要があります。<br />A レコードで登録することも可能ですが、その場合 Hashnode 組み込みの<br />グローバル CDN とエッジキャッシングが使用できないため、表示速度などに影響があります。</p>
<h3 id="5asw6kaz44gu44kr44k544k44oe44kk44k6">外観のカスタマイズ</h3>
<p>ヘッダーの色や、ブログのロゴ、記事一覧レイアウトなどの見た目をカスタマイズできます。<br />また利用者はデフォルトのテーマとダークモードを自由に切り替えることができますが、<br />アクセス時のデフォルトのテーマをダークモードとすることも可能です。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607358091997/pjZddfqBf.png" alt="image.png" /></p>
<p>記事ページ以外にも Markdown ベースで独自のページを追加することができます。<br />使い方としてはプロフィールページなどを追加しているユーザーが多いようです。<br />また Hashnode への投稿で特定の条件を満たすとバッジを獲得し、<br />BADGES ページに公開できます (非公開設定も可)。投稿のモチベーションの一つになります。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607358118532/rRbnz9ivl.png" alt="image.png" /></p>
<p>またブログ読者が登録可能なニュースレター機能も設定で有効無効を切り替えることできます。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607358164326/7GHScoJ7I.png" alt="image.png" /></p>
<h3 id="github-backup">GitHub Backup</h3>
<p>Hashnode の GitHub アプリをインストールし、バックアップ先のリポジトリを指定することで<br />Story (記事) の追加、変更を画像込み自動で GitHub にバックアップできます。</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607358184847/GiWCWTln1.png" alt="image.png" /></p>
<h3 id="import-and-export">Import &amp; Export</h3>
<p>Medium.com と Dev.to から既存のコンテンツを移行する機能が用意されています。<br />実際に Dev.to にポスト済みの記事で試してみましたが、画像込みで簡単に Import できました。<br />また Hashnode に投稿した全ての記事のデータを JSON 形式で Export する機能も用意されています。</p>
<h3 id="and">無料 &amp; 広告無し</h3>
<p>将来的には 個人や Team 向けの Proプラン (月額サブスクリプション) を導入し<br />収益化することも検討しているようです。</p>
<h3 id="3rd-party-integration">3rd Party Integration</h3>
<p>Hashnode 自体も記事毎の View 数などの Analytics 機能は持っているのですが<br />Google Analytics や Hotjar などのサイト分析ツールの ID を別途設定できます。</p>
<h3 id="5yo55uk5yywicjmipxjgzlpiq0p">収益化 (投げ銭)</h3>
<p>Hashnode 自体に収益化の機能はないのですが、前述の 3rd Party Integration の機能で<br /><a target="_blank" href="https://webmonetization.org/">Web Monetization</a> の <a target="_blank" href="https://webmonetization.org/docs/receiving/#payment-pointers">Payment Pointer</a> を埋め込むことが可能です。
これにより <a target="_blank" href="https://coil.com/">Coil</a> というサービスの課金ユーザーがブログを訪れた場合に<br />自動的にチップを受け取ることができます。</p>
<h3 id="ambassador-program">Ambassador program</h3>
<p>Hashnode を他のユーザーに紹介すると限定のカスタマイズ要素などをアンロックできるようです。<br />最後にこちらにリンクを張っておきます。</p>
<p>つ https://hashnode.com/@hayao-k/joinme</p>
<p>もしこの記事で興味を持っていただけたら、上記からポチッと登録いただけたら嬉しいです。</p>
<h2 id="5yc6icd">参考</h2>
<p><a target="_blank" href="https://slashism.com/why-should-you-start-your-dev-blog-on-hashnode">Why Should You Start Your Dev Blog On Hashnode</a><br /><a target="_blank" href="https://catalins.tech/why-hashnode-is-different-than-other-blogging-platforms">Why Hashnode Is Different Than Other Blogging Platforms</a><br /><a target="_blank" href="https://x.hashnode.dev/web-monetization-on-hashnode-ckdu9umr900jmn0s1gr0r4gis">Web Monetization on Hashnode</a></p>
<p>以上です。</p>
]]></content:encoded></item><item><title><![CDATA[A Beginner's Guide to Create AWS CDK Construct Library with projen]]></title><description><![CDATA[Introduction
AWS CDK allows you to create your own Construct Library and publish it to npm or PyPI.
Using projen makes the development of Construct Library very comfortable.
The content of this article has been tested with the following versions

pro...]]></description><link>https://hayao-k.dev/a-beginners-guide-to-create-aws-cdk-construct-library-with-projen</link><guid isPermaLink="true">https://hayao-k.dev/a-beginners-guide-to-create-aws-cdk-construct-library-with-projen</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-cdk]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Mon, 23 Nov 2020 06:29:22 GMT</pubDate><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>AWS CDK allows you to create your own Construct Library and publish it to npm or PyPI.</p>
<p>Using projen makes the development of Construct Library very comfortable.</p>
<p>The content of this article has been tested with the following versions</p>
<ul>
<li>projen: v0.3.162</li>
<li>AWS CDK: v1.73.0</li>
</ul>
<h2 id="what-is-projen">What is projen?</h2>
<p>projen is a tool for defining and managing increasingly complex project configurations in code.</p>
<p>https://github.com/projen/projen</p>
<p>With projen, you no longer need to manage files such as package.json by yourself.</p>
<p>projen does not only generate various files during project creation but also continuously updates and maintains these settings.</p>
<p>You can easily start a new project using the pre-defined project types.<br />As of November 2020, the following project types are supported.</p>
<pre><code>Commands:
  projen <span class="hljs-keyword">new</span> awscdk-app-ts     AWS CDK app <span class="hljs-keyword">in</span> TypeScript.
  projen <span class="hljs-keyword">new</span> awscdk-construct  AWS CDK construct <span class="hljs-keyword">library</span> project.
  projen <span class="hljs-keyword">new</span> cdk8s-construct   CDK8s construct <span class="hljs-keyword">library</span> project.
  projen <span class="hljs-keyword">new</span> jsii              Multi-language jsii <span class="hljs-keyword">library</span> project.
  projen <span class="hljs-keyword">new</span> nextjs            Next.js project without TypeScript.
  projen <span class="hljs-keyword">new</span> nextjs-ts         Next.js project <span class="hljs-keyword">with</span> TypeScript.
  projen <span class="hljs-keyword">new</span> node              Node.js project.
  projen <span class="hljs-keyword">new</span> project           Base project.
  projen <span class="hljs-keyword">new</span> react             React project without TypeScript.
  projen <span class="hljs-keyword">new</span> react-ts          React project <span class="hljs-keyword">with</span> TypeScript.
  projen <span class="hljs-keyword">new</span> typescript        TypeScript project.
  projen <span class="hljs-keyword">new</span> typescript-app    TypeScript app.
</code></pre><p>awscdk-construct creates an environment for building Contruct using <a target="_blank" href="https://github.com/aws/jsii">jsii</a>.<br />jsii allows you to generate libraries from TypeScript code to work in Python, Java, and .NET.</p>
<h2 id="create-project">Create project</h2>
<p>Create a Construct Library project with <code>projen new awscdk-construct</code>.</p>
<pre><code>$ mkdir cdk-sample-lib &amp;&amp; cd cdk-sample-lib

$ npx projen new awscdk-construct
<span class="hljs-symbol">npx:</span> installed <span class="hljs-number">63</span> <span class="hljs-keyword">in</span> <span class="hljs-number">8.37</span>s
🤖 Created .projenrc.js <span class="hljs-keyword">for</span> AwsCdkConstructLibrary
🤖 Synthesizing project...
$GIT_USER_NAME is <span class="hljs-keyword">not</span> <span class="hljs-keyword">defined</span>
</code></pre><p>Under the project directory, <code>.projenrc.js</code> has been created.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> { AwsCdkConstructLibrary } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'projen'</span>);

<span class="hljs-keyword">const</span> project = <span class="hljs-keyword">new</span> AwsCdkConstructLibrary({
  authorAddress: <span class="hljs-string">"user@domain.com"</span>,
  authorName: $GIT_USER_NAME,
  cdkVersion: <span class="hljs-string">"1.60.0"</span>,
  name: <span class="hljs-string">"cdk-sample-lib"</span>,
  repository: <span class="hljs-string">"https://github.com/user/cdk-sample-lib.git"</span>,
});

project.synth();
</code></pre>
<p>You can add dependencies on AWS CDKs and other modules to be used.</p>
<pre><code class="lang-ts">  cdkDependencies: [
    <span class="hljs-string">'@aws-cdk/core'</span>,
    <span class="hljs-string">'@aws-cdk/aws-apigatewayv2'</span>,
    <span class="hljs-string">'@aws-cdk/aws-apigatewayv2-integrations'</span>,
    <span class="hljs-string">'@aws-cdk/aws-lambda'</span>
  ],
  deps: [ 
    <span class="hljs-string">'super-useful-lib'</span> 
  ]
</code></pre>
<p>If you want to cross-compile to languages other than TypeScript with jsii, add the target language.</p>
<pre><code class="lang-ts">  python: {
    distName: <span class="hljs-string">'cdk-sample-lib'</span>,
    <span class="hljs-keyword">module</span>: 'cdk_sample_lib',
  },
</code></pre>
<p>See <a target="_blank" href="https://github.com/projen/projen/blob/master/API.md#projen-awscdkconstructlibrary">API.md</a> for other available options.<br />As an example, the modified file looks like this</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> { AwsCdkConstructLibrary } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'projen'</span>);

<span class="hljs-keyword">const</span> PROJECT_NAME = <span class="hljs-string">"cdk-sample-lib"</span> 

<span class="hljs-keyword">const</span> project = <span class="hljs-keyword">new</span> AwsCdkConstructLibrary({
  authorAddress: <span class="hljs-string">"hayaok333@gmail.com"</span>,
  authorName: <span class="hljs-string">"hayao-k"</span>,
  cdkVersion: <span class="hljs-string">"1.73.0"</span>,
  name: PROJECT_NAME,
  repository: <span class="hljs-string">"https://github.com/hayao-k/cdk-sample-lib.git"</span>,
  defaultReleaseBranch: <span class="hljs-string">'main'</span>,
  cdkDependencies: [ 
    <span class="hljs-string">'@aws-cdk/core'</span>,
    <span class="hljs-string">'@aws-cdk/aws-apigatewayv2'</span>,
    <span class="hljs-string">'@aws-cdk/aws-apigatewayv2-integrations'</span>,
    <span class="hljs-string">'@aws-cdk/aws-lambda'</span>
  ],
  python: {
    distName: PROJECT_NAME,
    <span class="hljs-keyword">module</span>: 'cdk_sample_lib',
  },
});

project.synth();
</code></pre>
<p>Once you have edited <code>.projenrc.js</code>, run the projen command to reflect the changes (yarn is required).</p>
<pre><code>$ npx projen
npx: installed 63 in 5.491s
🤖 Synthesizing project...
🤖 yarn <span class="hljs-keyword">install</span> <span class="hljs-comment">--check-files</span>
yarn <span class="hljs-keyword">install</span> v1<span class="hljs-number">.22</span><span class="hljs-number">.5</span>
info <span class="hljs-keyword">No</span> lockfile found.
[<span class="hljs-number">1</span>/<span class="hljs-number">5</span>] Validating package.json...
[<span class="hljs-number">2</span>/<span class="hljs-number">5</span>] Resolving packages...
[<span class="hljs-number">3</span>/<span class="hljs-number">5</span>] Fetching packages...
info fsevents@<span class="hljs-number">2.2</span><span class="hljs-number">.1</span>: The platform <span class="hljs-string">"linux"</span> <span class="hljs-keyword">is</span> incompatible <span class="hljs-keyword">with</span> this module.
info <span class="hljs-string">"fsevents@2.2.1"</span> <span class="hljs-keyword">is</span> an optional dependency <span class="hljs-keyword">and</span> <span class="hljs-keyword">failed</span> <span class="hljs-keyword">compatibility</span> check. <span class="hljs-keyword">Excluding</span> it <span class="hljs-keyword">from</span> installation.
[<span class="hljs-number">4</span>/<span class="hljs-number">5</span>] Linking dependencies...
[<span class="hljs-number">5</span>/<span class="hljs-number">5</span>] Building <span class="hljs-keyword">fresh</span> packages...
<span class="hljs-keyword">success</span> Saved lockfile.
Done <span class="hljs-keyword">in</span> <span class="hljs-number">20.34</span>s.
🤖 jest: * =&gt; ^<span class="hljs-number">26.6</span><span class="hljs-number">.3</span>
🤖 @types/jest: * =&gt; ^<span class="hljs-number">26.0</span><span class="hljs-number">.15</span>
🤖 ts-jest: * =&gt; ^<span class="hljs-number">26.4</span><span class="hljs-number">.4</span>
🤖 eslint: * =&gt; ^<span class="hljs-number">7.13</span><span class="hljs-number">.0</span>
🤖 eslint-<span class="hljs-keyword">import</span>-resolver-node: * =&gt; ^<span class="hljs-number">0.3</span><span class="hljs-number">.4</span>
🤖 eslint-<span class="hljs-keyword">import</span>-resolver-typescript: * =&gt; ^<span class="hljs-number">2.3</span><span class="hljs-number">.0</span>
🤖 eslint-<span class="hljs-keyword">plugin</span>-<span class="hljs-keyword">import</span>: * =&gt; ^<span class="hljs-number">2.22</span><span class="hljs-number">.1</span>
🤖 <span class="hljs-keyword">json</span>-<span class="hljs-keyword">schema</span>: * =&gt; ^<span class="hljs-number">0.2</span><span class="hljs-number">.5</span>
🤖 Synthesis <span class="hljs-keyword">complete</span>

<span class="hljs-comment">----------------------------------------------------------------------------------------------------</span>
Commands:

<span class="hljs-keyword">BUILD</span>
compile          <span class="hljs-keyword">Only</span> compile
watch            Watch &amp; compile <span class="hljs-keyword">in</span> the background
<span class="hljs-keyword">build</span>            <span class="hljs-keyword">Full</span> <span class="hljs-keyword">release</span> <span class="hljs-keyword">build</span> (<span class="hljs-keyword">test</span>+compile)

<span class="hljs-keyword">TEST</span>
<span class="hljs-keyword">test</span>             Run tests
<span class="hljs-keyword">test</span>:watch       Run jest <span class="hljs-keyword">in</span> watch <span class="hljs-keyword">mode</span>
eslint           Runs eslint against the codebase

<span class="hljs-keyword">RELEASE</span>
compat           Perform API <span class="hljs-keyword">compatibility</span> <span class="hljs-keyword">check</span> against latest <span class="hljs-keyword">version</span>
<span class="hljs-keyword">release</span>          Bumps <span class="hljs-keyword">version</span> &amp; push <span class="hljs-keyword">to</span> <span class="hljs-keyword">master</span>
docgen           Generate API.md <span class="hljs-keyword">from</span> .jsii manifest
bump             Commits a bump <span class="hljs-keyword">to</span> the <span class="hljs-keyword">package</span> <span class="hljs-keyword">version</span> based <span class="hljs-keyword">on</span> conventional commits
<span class="hljs-keyword">package</span>          <span class="hljs-keyword">Create</span> an npm tarball

MAINTAIN
projen           Synthesize <span class="hljs-keyword">project</span> configuration <span class="hljs-keyword">from</span> .projenrc.js
projen:<span class="hljs-keyword">upgrade</span>   upgrades projen <span class="hljs-keyword">to</span> the latest <span class="hljs-keyword">version</span>

MISC
<span class="hljs-keyword">start</span>            Shows this menu

Tips:
💡 The VSCode jest extension watches <span class="hljs-keyword">in</span> the background <span class="hljs-keyword">and</span> shows inline <span class="hljs-keyword">test</span> results
💡 <span class="hljs-keyword">Install</span> Mergify <span class="hljs-keyword">in</span> your GitHub repository <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> merges <span class="hljs-keyword">of</span> approved PRs
💡 <span class="hljs-keyword">Set</span> <span class="hljs-string">`autoUpgradeSecret`</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> projen <span class="hljs-keyword">upgrade</span> pull requests
💡 <span class="hljs-string">`API.md`</span> includes the API <span class="hljs-keyword">reference</span> <span class="hljs-keyword">for</span> your <span class="hljs-keyword">library</span>
💡 <span class="hljs-keyword">Set</span> <span class="hljs-string">"compat"</span> <span class="hljs-keyword">to</span> <span class="hljs-string">"true"</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> API breaking-<span class="hljs-keyword">change</span> <span class="hljs-keyword">validation</span>
</code></pre><p>You will see that projen automatically generates the package.json, the .gitignore, .npmignore, eslint, jsii configuration, license files, etc., as well as the creation and installation of the package.json.</p>
<p>You no longer have to copy from an existing project every time you create a new project.</p>
<p>Whenever you edit these files, you need to modify the .projenrc.js file and re-run the projen command.<br />If you edit them manually, the build will fail.</p>
<pre><code>$ tree -L <span class="hljs-number">1</span> -a
.
├── .eslintrc.json
├── .github
├── .gitignore
├── LICENSE
├── .mergify.yml
├── node_modules
├── .npmignore
├── package.json
├── .projenrc.js
├── README.md
├── src
├── test
├── tsconfig.jest.json
├── <span class="hljs-keyword">version</span>.json
├── .versionrc.json
└── yarn.<span class="hljs-keyword">lock</span>
</code></pre><h2 id="development">Development</h2>
<p>Let's try a simple example of calling Hello World's Lambda from the API Gateway (HTTP API).<br />Please note that the HTTP API L2 Constructs has an Experimental status as of November 2020.</p>
<p>The following directory has already been created by projen.</p>
<pre><code>.
├── lib/ 
├── src/
├── <span class="hljs-built_in">test</span>/
</code></pre><p>The lib directory will contain the compiled files.</p>
<p>The code for the Lambda functions can also be inserted inline into the CDK code, but in this example create index.js in the functions directory.</p>
<ul>
<li><code>functions/index.js</code></li>
</ul>
<pre><code class="lang-js"><span class="hljs-built_in">exports</span>.handler = <span class="hljs-keyword">async</span> (event) =&gt; {
    <span class="hljs-keyword">const</span> response = {
        <span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(<span class="hljs-string">'Hello from Lambda!'</span>),
    };
    <span class="hljs-keyword">return</span> response;
};
</code></pre>
<p>Create the following two files in the src directory</p>
<ul>
<li><code>src/index.ts</code></li>
</ul>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { HttpApi } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/aws-apigatewayv2'</span>;
<span class="hljs-keyword">import</span> { LambdaProxyIntegration } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/aws-apigatewayv2-integrations'</span>;
<span class="hljs-keyword">import</span> { Code, <span class="hljs-built_in">Function</span>, Runtime } <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/aws-lambda'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> cdk <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/core'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> CdkSampleLib <span class="hljs-keyword">extends</span> cdk.Construct {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">scope: cdk.Construct, id: <span class="hljs-built_in">string</span></span>) {
    <span class="hljs-built_in">super</span>(scope, id);

    <span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Function</span>(<span class="hljs-built_in">this</span>, <span class="hljs-string">'HelloWorld'</span>, {
      handler: <span class="hljs-string">'index.handler'</span>,
      code: Code.fromAsset(<span class="hljs-string">'functions'</span>),
      runtime: Runtime.NODEJS_12_X,
    });

    <span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> HttpApi(<span class="hljs-built_in">this</span>, <span class="hljs-string">'API'</span>, {
      defaultIntegration: <span class="hljs-keyword">new</span> LambdaProxyIntegration({ handler }),
    });

    <span class="hljs-keyword">new</span> cdk.CfnOutput(<span class="hljs-built_in">this</span>, <span class="hljs-string">'ApiURL'</span>, { value: api.url! });
  }
}
</code></pre>
<ul>
<li><code>src/integ.default.ts</code></li>
</ul>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> cdk <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/core'</span>;
<span class="hljs-keyword">import</span> { CdkSampleLib } <span class="hljs-keyword">from</span> <span class="hljs-string">'./index'</span>;

<span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> cdk.App();
<span class="hljs-keyword">const</span> stack = <span class="hljs-keyword">new</span> cdk.Stack(app, <span class="hljs-string">'MyStack'</span>);

<span class="hljs-keyword">new</span> CdkSampleLib(stack, <span class="hljs-string">'Cdk-Sample-Lib'</span>);
</code></pre>
<p>Create the following files in the test directory</p>
<ul>
<li><code>test/hello.test.ts</code></li>
</ul>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> cdk <span class="hljs-keyword">from</span> <span class="hljs-string">'@aws-cdk/core'</span>;
<span class="hljs-keyword">import</span> { CdkSampleLib } <span class="hljs-keyword">from</span> <span class="hljs-string">'../src/index'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'@aws-cdk/assert/jest'</span>;

test(<span class="hljs-string">'create app'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> app = <span class="hljs-keyword">new</span> cdk.App();
  <span class="hljs-keyword">const</span> stack = <span class="hljs-keyword">new</span> cdk.Stack(app);
  <span class="hljs-keyword">new</span> CdkSampleLib(stack, <span class="hljs-string">'TestStack'</span>);
  expect(stack).toHaveResource(<span class="hljs-string">'AWS::Lambda::Function'</span>);
  expect(stack).toHaveResource(<span class="hljs-string">'AWS::ApiGatewayV2::Api'</span>);
  expect(stack).toHaveResource(<span class="hljs-string">'AWS::ApiGatewayV2::Integration'</span>);
});
</code></pre>
<h2 id="unit-test">Unit Test</h2>
<p>Various scripts are predefined in the <code>package.json</code> generated from projen.</p>
<p>Run the test with <code>yarn test</code>.
<code>yarn build</code> also run test, so omit the example output here.</p>
<h2 id="build">Build</h2>
<p>Run <code>yarn build</code> and compile TypeScript to the jsii module.</p>
<p><a target="_blank" href="https://github.com/aws/jsii-docgen">jsii-docgen</a> generates API documentation (API.md) from comments in the code.</p>
<p>In addition, <a target="_blank" href="https://github.com/aws/jsii/tree/main/packages/jsii-pacmak">jsii-pacmak</a> creates language-specific public packages in the dist directory.</p>
<pre><code>$ yarn build
yarn run v1.<span class="hljs-number">22.5</span>
$ yarn run test &amp;&amp; yarn run compile &amp;&amp; yarn run package
$ rm -fr lib/ &amp;&amp; jest --passWithNoTests --updateSnapshot &amp;&amp; yarn run eslint
 PASS  test/hello.test.ts
  ✓ create app (<span class="hljs-number">197</span> ms)

----------<span class="hljs-params">|---------|</span>----------<span class="hljs-params">|---------|</span>---------<span class="hljs-params">|-------------------
File      |</span> % Stmts <span class="hljs-params">| % Branch |</span> % Funcs <span class="hljs-params">| % Lines |</span> Uncovered Line <span class="hljs-comment">#s </span>
----------<span class="hljs-params">|---------|</span>----------<span class="hljs-params">|---------|</span>---------<span class="hljs-params">|-------------------
All files |</span>     <span class="hljs-number">100</span> <span class="hljs-params">|      100 |</span>     <span class="hljs-number">100</span> <span class="hljs-params">|     100 |</span>                   
 index.ts <span class="hljs-params">|     100 |</span>      <span class="hljs-number">100</span> <span class="hljs-params">|     100 |</span>     <span class="hljs-number">100</span> <span class="hljs-params">|                   
----------|</span>---------<span class="hljs-params">|----------|</span>---------<span class="hljs-params">|---------|</span>-------------------
Test <span class="hljs-symbol">Suites:</span> <span class="hljs-number">1</span> passed, <span class="hljs-number">1</span> total
<span class="hljs-symbol">Tests:</span>       <span class="hljs-number">1</span> passed, <span class="hljs-number">1</span> total
<span class="hljs-symbol">Snapshots:</span>   <span class="hljs-number">0</span> total
<span class="hljs-symbol">Time:</span>        <span class="hljs-number">4.713</span> s, estimated <span class="hljs-number">7</span> s
Ran all test suites.
$ eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test
$ jsii --silence-warnings=reserved-word --no-fix-peer-dependencies &amp;&amp; jsii-docgen
$ jsii-pacmak
Done <span class="hljs-keyword">in</span> <span class="hljs-number">51.38</span>s.
</code></pre><p>Once the build is successful, let's try deploying locally.</p>
<pre><code>$ cdk deploy --app=<span class="hljs-string">'./lib/integ.default.js'</span>
This deployment will make potentially sensitive changes according to your current security approval level (--<span class="hljs-keyword">require</span>-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────────────────────┬────────┬───────────────────────┬──────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                     │ Effect │ Action                │ Principal                        │ Condition                                                                                                                           │
├───┼──────────────────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Cdk-Sample-Lib/HelloWorld.Arn}             │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com │ <span class="hljs-string">"ArnLike"</span>: {                                                                                                                        │
│   │                                              │        │                       │                                  │   <span class="hljs-string">"AWS:SourceArn"</span>: <span class="hljs-string">"arn:<span class="hljs-subst">${AWS::Partition}</span>:execute-api:<span class="hljs-subst">${AWS::Region}</span>:<span class="hljs-subst">${AWS::AccountId}</span>:<span class="hljs-subst">${CdkSampleLibAPI6FD5D6E6}</span>/*/*"</span>              │
│   │                                              │        │                       │                                  │ }                                                                                                                                   │
├───┼──────────────────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Cdk-Sample-Lib/HelloWorld/ServiceRole.Arn} │ Allow  │ sts:AssumeRole        │ Service:lambda.amazonaws.com     │                                                                                                                                     │
└───┴──────────────────────────────────────────────┴────────┴───────────────────────┴──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
IAM Policy Changes
┌───┬──────────────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                                 │ Managed Policy ARN                                                             │
├───┼──────────────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Cdk-Sample-Lib/HelloWorld/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴──────────────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes <span class="hljs-keyword">not</span> in this list. See https:<span class="hljs-regexp">//github</span>.com/aws/aws-cdk/issues/<span class="hljs-number">1299</span>)

Do you wish to deploy these changes (<span class="hljs-regexp">y/n)? y
MyStack: deploying...
[0%] start: Publishing 20472c64d312cc547a9359d36b04cfc75633027c0b13c3c07b96dfdf1b1c428f:current
[100%] success: Published 20472c64d312cc547a9359d36b04cfc75633027c0b13c3c07b96dfdf1b1c428f:current
MyStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (9/9)

 ✅  MyStack

Outputs:
MyStack.CdkSampleLibApiURL32C6192A = https:/</span><span class="hljs-regexp">/4p9zte6ny8.execute-api.ap-northeast-1.amazonaws.com/</span>

Stack ARN:
arn:aws:cloudformation:ap-northeast-<span class="hljs-number">1</span>:<span class="hljs-number">123456789012</span>:stack/MyStack/<span class="hljs-number">8</span>fc3865<span class="hljs-number">0</span>-<span class="hljs-number">2</span>a78-<span class="hljs-number">11</span>eb-<span class="hljs-number">8</span>d53-0a6e476ede3<span class="hljs-number">0</span>
</code></pre><p>You can check the response of the Lambda function from the output API URL.</p>
<pre><code>$ curl <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/4p9zte6ny8.execute-api.ap-northeast-1.amazonaws.com/</span>
<span class="hljs-string">"Hello from Lambda!"</span>
</code></pre><p>To remove it, run the cdk destory.</p>
<pre><code>$ cdk destroy --app=<span class="hljs-string">'./lib/integ.default.js'</span>                                        
Are you sure you want to <span class="hljs-symbol">delete:</span> MyStack (y/n)? y
<span class="hljs-symbol">MyStack:</span> destroying...
<span class="hljs-number">5</span><span class="hljs-symbol">:</span><span class="hljs-number">27</span><span class="hljs-symbol">:</span><span class="hljs-number">29</span> PM <span class="hljs-params">| DELETE_IN_PROGRESS   |</span> AWS::CloudFormation::Stack <span class="hljs-params">| MyStack

 ✅  MyStack: destroyed</span>
</code></pre><h2 id="release">Release</h2>
<p>First, commit the changes.</p>
<pre><code>$ git <span class="hljs-keyword">add</span> . 
$ git <span class="hljs-keyword">commit</span> -m "feat: release 0.0.1"
</code></pre><p><code>yarn release</code> bumps the version and automatically updates <code>CANGELOG.md</code>.
Then push to the release branch of GitHub.</p>
<pre><code>$ yarn <span class="hljs-keyword">release</span>
yarn run v1<span class="hljs-number">.22</span><span class="hljs-number">.5</span>
$ yarn run <span class="hljs-comment">--silent no-changes || (yarn run bump &amp;&amp; git push --follow-tags origin main)</span>
$ yarn run <span class="hljs-comment">--silent no-changes || standard-version</span>
✔ bumping <span class="hljs-keyword">version</span> <span class="hljs-keyword">in</span> version.json <span class="hljs-keyword">from</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span> <span class="hljs-keyword">to</span> <span class="hljs-number">0.0</span><span class="hljs-number">.1</span>
✔ Running lifecycle script <span class="hljs-string">"postbump"</span>
ℹ - <span class="hljs-keyword">execute</span> command: <span class="hljs-string">"yarn run projen &amp;&amp; git add ."</span>
🤖 yarn <span class="hljs-keyword">install</span> <span class="hljs-comment">--check-files</span>
🤖 Synthesis <span class="hljs-keyword">complete</span>

<span class="hljs-comment">----------------------------------------------------------------------------------------------------</span>
Commands:

<span class="hljs-keyword">BUILD</span>
compile          <span class="hljs-keyword">Only</span> compile
watch            Watch &amp; compile <span class="hljs-keyword">in</span> the background
<span class="hljs-keyword">build</span>            <span class="hljs-keyword">Full</span> <span class="hljs-keyword">release</span> <span class="hljs-keyword">build</span> (<span class="hljs-keyword">test</span>+compile)

<span class="hljs-keyword">TEST</span>
<span class="hljs-keyword">test</span>             Run tests
<span class="hljs-keyword">test</span>:watch       Run jest <span class="hljs-keyword">in</span> watch <span class="hljs-keyword">mode</span>
eslint           Runs eslint against the codebase

<span class="hljs-keyword">RELEASE</span>
compat           Perform API <span class="hljs-keyword">compatibility</span> <span class="hljs-keyword">check</span> against latest <span class="hljs-keyword">version</span>
<span class="hljs-keyword">release</span>          Bumps <span class="hljs-keyword">version</span> &amp; push <span class="hljs-keyword">to</span> <span class="hljs-keyword">main</span>
docgen           Generate API.md <span class="hljs-keyword">from</span> .jsii manifest
bump             Commits a bump <span class="hljs-keyword">to</span> the <span class="hljs-keyword">package</span> <span class="hljs-keyword">version</span> based <span class="hljs-keyword">on</span> conventional commits
<span class="hljs-keyword">package</span>          <span class="hljs-keyword">Create</span> an npm tarball

MAINTAIN
projen           Synthesize <span class="hljs-keyword">project</span> configuration <span class="hljs-keyword">from</span> .projenrc.js
projen:<span class="hljs-keyword">upgrade</span>   upgrades projen <span class="hljs-keyword">to</span> the latest <span class="hljs-keyword">version</span>

MISC
<span class="hljs-keyword">start</span>            Shows this menu

Tips:
💡 The VSCode jest extension watches <span class="hljs-keyword">in</span> the background <span class="hljs-keyword">and</span> shows inline <span class="hljs-keyword">test</span> results
💡 <span class="hljs-keyword">Install</span> Mergify <span class="hljs-keyword">in</span> your GitHub repository <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> merges <span class="hljs-keyword">of</span> approved PRs
💡 <span class="hljs-keyword">Set</span> <span class="hljs-string">`autoUpgradeSecret`</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> projen <span class="hljs-keyword">upgrade</span> pull requests
💡 <span class="hljs-string">`API.md`</span> includes the API <span class="hljs-keyword">reference</span> <span class="hljs-keyword">for</span> your <span class="hljs-keyword">library</span>
💡 <span class="hljs-keyword">Set</span> <span class="hljs-string">"compat"</span> <span class="hljs-keyword">to</span> <span class="hljs-string">"true"</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">enable</span> <span class="hljs-keyword">automatic</span> API breaking-<span class="hljs-keyword">change</span> <span class="hljs-keyword">validation</span>

✔ created CHANGELOG.md
✔ outputting changes <span class="hljs-keyword">to</span> CHANGELOG.md
✔ committing version.json <span class="hljs-keyword">and</span> CHANGELOG.md <span class="hljs-keyword">and</span> <span class="hljs-keyword">all</span> staged files
✔ tagging <span class="hljs-keyword">release</span> v0<span class="hljs-number">.0</span><span class="hljs-number">.1</span>
ℹ Run <span class="hljs-string">`git push --follow-tags origin master`</span> <span class="hljs-keyword">to</span> publish
</code></pre><p>Or you can bump to any version by running <code>yarn bump</code>.</p>
<pre><code><span class="hljs-string">$</span> <span class="hljs-string">yarn</span> <span class="hljs-string">bump</span> <span class="hljs-string">--release-as</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">git</span> <span class="hljs-string">push</span> <span class="hljs-string">--follow-tags</span> <span class="hljs-string">origin</span> <span class="hljs-string">main</span>
</code></pre><p>The Github Actions workflow definition is also generated when the projen command is executed, which makes it easy to automate the release to the package repository.</p>
<ul>
<li><p>Build workflow (.github/workflows/build.yaml)
Runs when a pull request is created.<br />Builds the library and checks for tampering (i.e., manual modification).</p>
</li>
<li><p>Release workflow (.github/workflows/release.yaml):
It is triggered by push to the release branch.<br />After building in the release branch, it automatically publishes to the repositories, such as npm and PyPI by <a target="_blank" href="https://github.com/aws/jsii-release">jsii-release</a>.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606750440556/f2Q7bQLVJ.png" alt="Alt Text" /></p>
<p>In order for the Release job to work properly, you need to register the Secrets for the language you want to publish in the GitHub repository.</p>
<ul>
<li>npm: NPM_TOKEN</li>
<li>.NET: NUGET_API_KEY</li>
<li>Java: MAVEN_GPG_PRIVATE_KEY, MAVEN_GPG_PRIVATE_KEY_PASSPHRASE, MAVEN_PASSWORD, MAVEN_USERNAME, MAVEN_STAGING_PROFILE_ID</li>
<li>Python: TWINE_USERNAME, TWINE_PASSWORD</li>
</ul>
<h2 id="try-it">Try It!</h2>
<p>With projen, you can focus on the implementation of the Construct Library (and of course, on the regular CDK App).</p>
<p>Do you want to understand how to use projen in videos?<br />The following video published by <a target="_blank" href="https://www.youtube.com/channel/UCWGGdWsfeSXwubErtDQSdCg">@pahud</a>, an AWS Developer Advocate, is very helpful.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=cTsSXYOYQPw">https://www.youtube.com/watch?v=cTsSXYOYQPw</a></div>
<p>I recently published a construct library called cdk-ecr-image-scan-notify using projen.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/hayao-k/cdk-ecr-image-scan-notify">https://github.com/hayao-k/cdk-ecr-image-scan-notify</a></div>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/hayaok3/status/1323593051319668737">https://twitter.com/hayaok3/status/1323593051319668737</a></div>
<p>I hope this article will help you.</p>
]]></content:encoded></item><item><title><![CDATA[How to Manage AWS SSO Account Assignments in CloudFormaton]]></title><description><![CDATA[Here's a sample template
https://github.com/hayao-k/aws-sso-cloudformation-sample
Introduction
On 9/10/2020, AWS SSO API (sso-admin) was finally added to AWS Single Sign-On and operations through the AWS CLI/SDK and CloudFormation are now supported.
...]]></description><link>https://hayao-k.dev/how-to-manage-aws-sso-account-assignments-in-cloudformaton</link><guid isPermaLink="true">https://hayao-k.dev/how-to-manage-aws-sso-account-assignments-in-cloudformaton</guid><category><![CDATA[AWS]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Mon, 21 Sep 2020 16:47:53 GMT</pubDate><content:encoded><![CDATA[<h2 id="heres-a-sample-template">Here's a sample template</h2>
<p>https://github.com/hayao-k/aws-sso-cloudformation-sample</p>
<h2 id="introduction">Introduction</h2>
<p>On 9/10/2020, AWS SSO API (sso-admin) was finally added to AWS Single Sign-On and operations through the AWS CLI/SDK and CloudFormation are now supported.</p>
<p>🚀 <a target="_blank" href="https://aws.amazon.com/jp/about-aws/whats-new/2020/09/aws-single-sign-on-adds-account-assignment-apis-and-aws-cloudformation-support-to-automate-multi-account-access-management/">AWS Single Sign-On adds account assignment APIs and AWS CloudFormation support to automate multi-account access management</a></p>
<p>Until now, operations such as permission sets and assigning users/groups to AWS accounts had to be configured manually in the console.<br />This update paves the way for automating account assignment settings and getting to IaC!</p>
<h2 id="supported-cloudformation-resources">Supported CloudFormation resources</h2>
<p>As of September 2020, CloiudFormation will support the following two resources.</p>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-permissionset.html">AWS::SSO::PermissionSet</a>
Specifies the permissions to be set on the AWS SSO instance.</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-assignment.html">AWS::SSO::Assignment</a>
Assign access to your AWS account using the specified permission set.</p>
</li>
</ul>
<h2 id="points-to-note">Points to note</h2>
<p>Creating an AWS SSO resource in CloudFormation requires various IDs such as InstanceArn, Identiy-Store-Id, UserId, and GroupId.<br />These IDs are not display in the AWS SSO console and must be obtained via API.<br />To use the AWS SSO API (sso-admin), make sure you have AWS CLI version 1.18.136 or 2.0.48 or higher.</p>
<h2 id="example-of-a-permission-set">Example of a permission set</h2>
<p>This is an example of creating a simple set of permissions using AWS management policies.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">PermissionSet:</span>
  <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::SSO::PermissionSet</span>
  <span class="hljs-attr">Properties:</span>
    <span class="hljs-attr">InstanceArn:</span> <span class="hljs-string">'arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx'</span>
    <span class="hljs-attr">Name:</span> <span class="hljs-string">'AdministratorAccess'</span>
    <span class="hljs-attr">ManagedPolicies:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'arn:aws:iam::aws:policy/AdministratorAccess'</span>
</code></pre>
<p>For InstanceArn, specify the ARN of the SSO instance.
This value is obtained from list-instances in sso-admin.</p>
<pre><code class="lang-shell">$ aws sso-admin list-instances
{
    "Instances": [
        {
            "InstanceArn": "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx",
            "IdentityStoreId": "d-xxxxxxxxxx"
        }
    ]
}
</code></pre>
<h2 id="example-of-account-assignment">Example of Account Assignment</h2>
<p>Specify the AWS account, privilege set, and principal as follows.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">Assignment:</span> 
  <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::SSO::Assignment</span>
  <span class="hljs-attr">Properties:</span> 
    <span class="hljs-attr">InstanceArn:</span> <span class="hljs-string">'arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx'</span>
    <span class="hljs-attr">PermissionSetArn:</span> <span class="hljs-string">'arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxxx'</span>
    <span class="hljs-attr">TargetId:</span> <span class="hljs-string">'123456789012'</span>
    <span class="hljs-attr">TargetType:</span> <span class="hljs-string">AWS_ACCOUNT</span>
    <span class="hljs-attr">PrincipalId:</span> <span class="hljs-string">'f81d4fae-7dec-11d0-a765-00a0c91e6bf6'</span>
    <span class="hljs-attr">PrincipalType:</span> <span class="hljs-string">'GROUP'</span>
</code></pre>
<p>You can check the PermissionSetArn with sso-admin's describe-permission-set.</p>
<pre><code class="lang-shell">$ aws sso-admin list-permission-sets \
&gt; --instance-arn arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx 
{
    "PermissionSets": [
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxxx",
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-yyyyyyyyyyyyyyyy",
        "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxxx/ps-zzzzzzzzzzzzzzzz"
    ]
}

$ aws sso-admin describe-permission-set \
&gt; --instance-arn arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxx \
&gt; --permission-set-arn arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxx
{
    "PermissionSet": {
        "Name": "AdministratorAccess",
        "PermissionSetArn": "arn:aws:sso:::permissionSet/ssoins-xxxxxxxxxxxxxxx/ps-xxxxxxxxxxxxxxx",
        "CreatedDate": "2020-09-09T19:01:06.758000+09:00",
        "SessionDuration": "PT1H"
    }
}
</code></pre>
<p>If you have created a permission set in CloudFormation, use the built-in Fn::GetAtt function.</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">PermissionSetArn:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">LogicalId.PermissionSetArn</span>
</code></pre>
<p>Only AWS_ACCOUNT can be specified for TargetType.
Specify the AWS account ID to be assigned for TargetId.</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">TargetId:</span> <span class="hljs-string">'123456789012'</span>
    <span class="hljs-attr">TargetType:</span> <span class="hljs-string">AWS_ACCOUNT</span>
</code></pre>
<p>PrincipalType can be specified as either USER or GROUP.<br />PrincipalId must be specified as the GUID of the user/group to be assigned.</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">PrincipalId:</span> <span class="hljs-string">'f81d4fae-7dec-11d0-a765-00a0c91e6bf6'</span>
    <span class="hljs-attr">PrincipalType:</span> <span class="hljs-string">'GROUP'</span>
</code></pre>
<p>Many operations of the AWS SSO API (sso-admin) rely on user and group identifiers called principals.<br />Use the AWS SSO Identity Store API (identitystore) to get the GUIDs for a user/group.</p>
<p>Specify the identity store identifier obtained from list-instances in sso-admin for <code>--identity-store-id</code>.<br />For list-users you can specify a UserName and for list-group you can specify a DisplayName as filter.</p>
<pre><code class="lang-shell">$ aws identitystore list-users \
&gt; --identity-store-id d-xxxxxxxxxx \
&gt; --filters AttributePath=UserName,AttributeValue="user@example.com"
{
    "Users": [
        {
            "UserName": "userXX@examle.com",
            "UserId": "f81d4faxge-7dec11d8-a765-3at5-80e4-00a0c91e6bf6"
        }
    ]
}

$ aws identitystore list-groups \
&gt; --identity-store-id d-xxxxxxxxxx \
&gt; --filters AttributePath=DisplayName,AttributeValue="TestGroup"
{
    "Groups": [
        {
            "GroupId": "f81d4faxge-789fcfa5-005c-4379-89ba-10a11e641c17"
            "DisplayName": "TestGroup"
        }
    ]
}
</code></pre>
<p>See the GitHub link at the top for the entire template.</p>
<h2 id="summary">Summary</h2>
<ul>
<li>The AWS SSO API (sso-admin) has been added, enabling partial automation of administrative tasks and IaC</li>
<li>CloudFormation supports permission set creation and account assignment</li>
<li>User/Group IDs needed to identify principals must be obtained through the IdentityStore API<ul>
<li>Full automation seems to be difficult at the moment because of the need to search for IDs when assigning accounts</li>
<li>AWS SSO console does not display these IDs and ARNs, which is a bit inconvenient</li>
</ul>
</li>
<li>That said, it's exciting to be able to manage AWS SSO with APIs!</li>
</ul>
<h2 id="references">References</h2>
<p>AWS CloudFormation User Guide - SSO resource type reference<br />https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_SSO.html</p>
<p>AWS CLI Command Reference - sso-admin<br />https://awscli.amazonaws.com/v2/documentation/api/latest/reference/sso-admin/index.html</p>
<p>AWS CLI Command Reference - identitystore<br />https://awscli.amazonaws.com/v2/documentation/api/latest/reference/identitystore/index.html</p>
]]></content:encoded></item><item><title><![CDATA[Amazon ECR Image Scan Results with Slack Notification]]></title><description><![CDATA[Overview
Amazon EventBridge (CloudWatch Events) detects the image scan execution and starts the Lambda function.The Lambda function uses the DescribeImages API to get a summary of the scan results, formatting them and notifying Slack.

Example of not...]]></description><link>https://hayao-k.dev/amazon-ecr-image-scan-results-with-slack-notification</link><guid isPermaLink="true">https://hayao-k.dev/amazon-ecr-image-scan-results-with-slack-notification</guid><category><![CDATA[AWS]]></category><category><![CDATA[lambda]]></category><category><![CDATA[Docker]]></category><category><![CDATA[slack]]></category><dc:creator><![CDATA[hayao_k]]></dc:creator><pubDate>Sat, 16 May 2020 15:24:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1606750454793/X1CjUKiDM.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="overview">Overview</h2>
<p>Amazon EventBridge (CloudWatch Events) detects the image scan execution and starts the Lambda function.<br />The Lambda function uses the DescribeImages API to get a summary of the scan results, formatting them and notifying Slack.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606750448939/sybCW86CP.png" alt="Alt Text" /></p>
<h2 id="example-of-notification">Example of notification</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606750450713/Ho1cwvzpd.png" alt="Alt Text" /></p>
<p>Click on an image name to go to the scan results page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606750452602/kuNw8IL3E.png" alt="Alt Text" /></p>
<h2 id="getting-started">Getting Started</h2>
<p>The source code of Lambda function is here.  </p>
<p>https://github.com/hayao-k/ecr-image-scan-findings-to-slack/blob/master/lambda_function.py</p>
<p>Use python 3.7 or 3.8 for runtime.<br />The latest AWS SDK (boto3) is required to get a summary of the scan results.<br />You can include it in the function deployment package, but I recommend using Lambda Layers.<br />Allow ecr:DescribeImages in Lambda's execution role.<br />You need to set Slack's WEBHOOK_URL in the environment variable.</p>
<p>When the image scan is complete, the following event will be fired in the Event Bridge.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0"</span>,
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"85fc3613-e913-7fc4-a80c-a3753e4aa9ae"</span>,
    <span class="hljs-attr">"detail-type"</span>: <span class="hljs-string">"ECR Image Scan"</span>,
    <span class="hljs-attr">"source"</span>: <span class="hljs-string">"aws.ecr"</span>,
    <span class="hljs-attr">"account"</span>: <span class="hljs-string">"123456789012"</span>,
    <span class="hljs-attr">"time"</span>: <span class="hljs-string">"2019-10-29T02:36:48Z"</span>,
    <span class="hljs-attr">"region"</span>: <span class="hljs-string">"us-east-1"</span>,
    <span class="hljs-attr">"resources"</span>: [
        <span class="hljs-string">"arn:aws:ecr:us-east-1:123456789012:repository/my-repo"</span>
    ],
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"scan-status"</span>: <span class="hljs-string">"COMPLETE"</span>,
        <span class="hljs-attr">"repository-name"</span>: <span class="hljs-string">"my-repo"</span>,
        <span class="hljs-attr">"image-digest"</span>: <span class="hljs-string">"sha256:7f5b2640fe6fb4f46592dfd3410c4a79dac4f89e4782432e0378abcd1234"</span>,
        <span class="hljs-attr">"image-tags"</span>: []
    }
}
</code></pre>
<p>The describe_images method retrieves a summary of the scan results.</p>
<p><strong>Boto 3 Documentation ECR</strong><br />https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.describe_images</p>
<p>This is an example of a describe_images response.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"imageDetails"</span>: [
        {
            <span class="hljs-attr">"registryId"</span>: <span class="hljs-string">"123456789012"</span>,
            <span class="hljs-attr">"repositoryName"</span>: <span class="hljs-string">"amazonlinux"</span>,
            <span class="hljs-attr">"imageDigest"</span>: <span class="hljs-string">"sha256:7f5b2640fe6fb4f46592dfd3410c4a79dac4f89e4782432e0378abcd1234"</span>,
            <span class="hljs-attr">"imageTags"</span>: [
                <span class="hljs-string">"2.0.20190115"</span>
            ],
            <span class="hljs-attr">"imageSizeInBytes"</span>: <span class="hljs-number">61283455</span>,
            <span class="hljs-attr">"imagePushedAt"</span>: <span class="hljs-number">1572489492.0</span>,
            <span class="hljs-attr">"imageScanStatus"</span>: {
                <span class="hljs-attr">"status"</span>: <span class="hljs-string">"COMPLETE"</span>,
                <span class="hljs-attr">"description"</span>: <span class="hljs-string">"The scan was completed successfully."</span>
            },
            <span class="hljs-attr">"imageScanFindingsSummary"</span>: {
                <span class="hljs-attr">"imageScanCompletedAt"</span>: <span class="hljs-number">1572489494.0</span>,
                <span class="hljs-attr">"vulnerabilitySourceUpdatedAt"</span>: <span class="hljs-number">1572454026.0</span>,
                <span class="hljs-attr">"findingSeverityCounts"</span>: {
                    <span class="hljs-attr">"HIGH"</span>: <span class="hljs-number">9</span>,
                    <span class="hljs-attr">"LOW"</span>: <span class="hljs-number">5</span>,
                    <span class="hljs-attr">"MEDIUM"</span>: <span class="hljs-number">18</span>
                }
            }
        }
    ]
}
</code></pre>
<p>To detect only scan completion events, a custom event pattern is specified in the creation of a new rule for EventBridge.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"source"</span>: [
    <span class="hljs-string">"aws.ecr"</span>
  ],
  <span class="hljs-attr">"detail-type"</span>: [
    <span class="hljs-string">"ECR Image Scan"</span>
  ]
}
</code></pre>
<h2 id="references">References</h2>
<p><strong>Image Scanning</strong><br />https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html<br /><strong>Events and EventBridge</strong><br />https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr-eventbridge.html</p>
]]></content:encoded></item></channel></rss>