私の戦闘力は53万です

awsとgcpについて書きます

serverlessテスト手法(エミュレータなし)について書いてみる

このブログはAWS LambdaとServerless Advent Calendar 2021の16日目です。

qiita.com

サーバレスのテスト手法は様々なパターンがあり、
私も勉強会で情報収集するようにしていました。
ただ、結局のところ何が良いのかよく悩んでいたところではありました。

そんな折、AWSAtsushi Fukui (@afukui) | Twitterさんが
以前勉強会で、ローカルでの検証方法を語られていたり、
(該当部分に時間合わせてますが、この勉強会面白かったので、ぜひ全体見てください)

youtu.be

また、本アドベントカレンダーの5日目の
kensh (@_kensh) | Twitterさんのyoutube動画での
エミュレーションなしのテスト方法が
割と普段自分が実施している方法に近いのではと思ったことから
私の理解まとめと、具体的なコードを公開してみようと思いました。

youtu.be

本カレンダーではサーバレスやlambdaに詳しい方が
たくさんおられると思うので私の理解が間違っていたり、
もっとこうした方が良いといった
フィードバックやツッコミが頂けるかもという期待もあります。
ぜひお待ちしています!

サンプル

本記事での説明をより分かりやすくするため
サンプルでSAMのpython3.7のソースを公開しました。 github.com

dockerfile等はvscoderemote container
動作確認した時のものですので、
remote containerをお使いの方は是非ご利用ください。

コードを説明

lambdaのコードから見ます。
lambda起動時に実行されるlambda_handlerとは別の関数を書きます。
サンプルコードではfunction_sampleが該当します。
ここでは単純に2つの引数を足し算するだけです。

lambdaのコード

app.py

import os
.......(略)

def function_sample(num1: int, num2: int) -> int:
    res = num1 + num2
    return res


def lambda_handler(event, context):

    print(function_sample(3,4))

    return {
        "statusCode": 200
    }

テストコード

テストコードでは、上記関数(function_sample)を呼び出します。

tests/unit/test_hello.py

from functions.hello_world import app as app_hello

.......(略)

def test_function_sample():
    assert app_hello.function_sample(2, 3) == 5

テスト実行

deployはせず、ローカルでpytestを実行する形になります。

pytest  -k test_function_sample

こちらが、kensh (@_kensh) | Twitterさんの下記スライドの
エミュレータなしの方法に近いのかと思います。 f:id:remmemento:20211216214853p:plain

samやcdkの場合、buildやdeployをすると時間がかかってしまいますが
ローカルでソース変更してpytest実行するだけであれば
すぐに実行できます。

こうしてfunction_sampleが完成したら、
lambda_handlerからfunction_sampleを呼び出します。
サンプルのこの部分です

メインのロジック部分はこの方法で書いて
おおよそのバグを潰しておいて、
単純なコードだけではテストが難しい部分
(他サービスとの連携テストや権限周りのテストなど)は
deployして実際の環境で実施する形となります。

補足と考え方

ソース修正からテスト実行までの時間が短いことの価値は
Takuto Wada (@t_wada) | Twitterさんの
講演が分かりやすいと思います。
youtu.be

また、テスタビリティ向上のため、
外部との接続部分と、処理のロジックを切り分けるような話は
下記が分かりやすいと思いました。
こちらもサンプルコードを公開されています。 qiita.com

実は、この記事を見て、その分かりやすさに感動し、
自分も何か同じカレンダーで記事書いてみたい思い、
本記事を書いてみようと思いました。

あと理想と現実のバランスの取り方という点で、
以前クラメソさんで開催されていた勉強会がありがたかったです。 記事こちらです。 logmi.jp

また、本カレンダー6日目でhotswap的な手法も紹介されており、
最近使えるようになった手法で、こちらも開発段階では重宝しそうと思いました。
qiita.com

さいごに

もっと良い方法があれば是非知りたいのでコメント頂けると嬉しいです。
またカレンダー後半も楽しみにしています!
ありがとうございました。

DevOps Guru for RDS を触ってみた

こちらJapan APN Ambassador Advent Calendar 2021の11日目の記事です。

qiita.com

f:id:remmemento:20211211231551p:plain

今年もre:Inventでたくさん発表がありましたね。
発表の中で気になった DevOps Guru for RDS を触ってみたので
ブログ書きたいと思います。

準備

こちらサポートされているのが現時点で
AuroraのみのようなのでAuroraを立ち上げます。 docs.aws.amazon.com

立ち上がってしばらくしてからAuroraに負荷をかけてみます。
※DevOps Guru for RDSは機械学習による異常検知なので、
作成後すぐに負荷をかけても異常検知されない可能性があります。
私も適当に負荷をかけていたのですが、 すぐには検知されず、
下記で異常を検知させることができました。

host="xxxxxxxxxxxxxx.rds.amazonaws.com"
mysqlslap --delimiter=";" -u admin -p -h ${host} -P 3306 mydb --auto-generate-sql --concurrency=200 --number-of-queries=10000 --auto-generate-sql-write-number=2000 --number-int-cols=2 --number-char-cols=3

検知結果を見てみる

上記方法でAuroraに負荷かけ、異常が検出されたので見てみました。
insightsの一覧に表示されます。
また、この検知はeventBridgeやSNSと連携が可能です。
f:id:remmemento:20211211232137p:plain

今回はwriteのlatencyが異常だと検知されました。
詳細を開くと、重要度や開始時間が書かています。
インシデント対応の時には、報告の際に、この種の情報が必要になるので
まとめてくれているのは嬉しいですね。 f:id:remmemento:20211211232156p:plain

関連するメトリクスも提案表示してくれる

グラフを確認すると、関連するメトリクスもまとめてくれていました。
今回はwriteLatencyで検知されていたのですが、
同タイミングでコネクション数も増やしていたため、
これも怪しいのでは?ということで並べて表示してくれているようでした。

f:id:remmemento:20211211233804p:plain

AWS関連の操作もまとめてくれる

また、Relevant eventsとして、
直近で実施したAWS関連の操作もピックアップしてくれていました。
例えば私は、この検証をする前に、
別の検証でcloudformation stackを削除していたのですが、
それも関連しそうなイベントとして一覧表示してくれていました。
f:id:remmemento:20211211234435p:plain

Performance insightも併用

また、こちらは以前からあった機能ですが、 RDSのPerformance insightと併せて、 原因となるqueryの候補を確認できます。 f:id:remmemento:20211211234657p:plain

試してみて

DevOps Guru for RDSを使ってみた感覚としては
RDSのメトリクスにanomaly detectionがかけられているような感覚でした。
また、パフォーマンス面で何かあった時に
簡単に見たいものが揃っていて、率直に嬉しいサービスと思いました。

私は前職でOracleで性能問題の対応をしたことがあったのですが、
その時こんなツールがあったら良かったなと昔を思い出してしまいました。
DBの理解には@odakeiji1さんの書籍に大変お世話になりました。
特にお気に入りはこの本です。
www.amazon.co.jp

今はAWSに所属されているようで、
これからも大いに学ばせて頂きます。 twitter.com

Ambassadorとして楽しく記事を書かせて頂きました。
ありがとうございました!

amplify studioを触ってみた

f:id:remmemento:20211204192448p:plain

re:invent 2021で発表のあったamplify studioを触ってみました。

概要としては、figmaというサービスと連携し
GUIベースでコンポーネント
作成できるようになったようです。
今回は試しにカードのcomponetを作ってみました
f:id:remmemento:20211204205503p:plain

figmaでアカウント作成

まずはfigmaでアカウント作成し UI figma kitを利用可能なよう設定します。 www.figma.com

f:id:remmemento:20211204205919p:plain

figmaで適当なコンポーネントを作成

figmaで適当なコンポーネントを作成してみます。
figma上でコンポーネント化しておくことで
amplify側にコンポーネントとして連携できるようになるようなので
自分が作成したいコンポーネントが出来上がったら
create componetしておくと良いと思います。 f:id:remmemento:20211204205602p:plain

figmaとamplifyを連携

コンポーネントを作成したら、
それをamplify側に持ってきます。 awsコンソールから、
amplify studio ( 旧名 amplify admin UI)を有効化し
amplify studioにログインしておきます

f:id:remmemento:20211204211203p:plain

UI Libraryが追加されていますので選択します。 f:id:remmemento:20211204210711p:plain そしてfigmaと連携するために、 figmaでURLを取得し、Amplify側に入力します。 f:id:remmemento:20211204210439p:plain

初期でサンプルで作成されているコンポーネントも含めて
連携するかどうかを聞かれますので、
必要に応じてRejectかAcceptを選択します f:id:remmemento:20211204211427p:plain

あとはamplifyでamplify pullすれば
上記で作成したコンポーネントのコードが
自動作成されダウンロードされます。

実際に作成されたコード

内部が気になったのでコードを見てみました。
@aws-amplify/ui-react自体がText等のコンポーネントを持っており、
propsで、位置等の情報を指定可能なよう作成されているようでした。

/***************************************************************************
 * The contents of this file were generated with Amplify FrontendManager.           *
 * Please refrain from making any modifications to this file.              *
 * Any changes to this file will be overwritten when running amplify pull. *
 **************************************************************************/

/* eslint-disable */
import React from "react";
import { getOverrideProps } from "@aws-amplify/ui-react/internal";
import { Icon, Text, View } from "@aws-amplify/ui-react";
export default function Component1(props) {
  const { Title, text, overrides: overridesProp, ...rest } = props;
  const overrides = { ...overridesProp };
  return (
    <View
      width="449px"
      padding="0px 0px 0px 0px"
      position="relative"
      height="264px"
      {...rest}
      {...getOverrideProps(overrides, "View")}
    >
      <View
        padding="0px 0px 0px 0px"
        backgroundColor="rgba(245.00000059604645,245.00000059604645,245.00000059604645,1)"
        top="0px"
        left="0px"
        width="449px"
        position="absolute"
        height="264px"
        {...getOverrideProps(overrides, "View.View[0]")}
      ></View>
      <View
        padding="0px 0px 0px 0px"
        backgroundColor="rgba(25.999998450279236,188.00003439188004,254.00000005960464,0.2199999988079071)"
        top="0px"
        left="0px"
        width="449px"
        position="absolute"
        height="63px"
        {...getOverrideProps(overrides, "View.View[1]")}
      ></View>
      <Icon
        pathData="M96 19C96 29.4934 74.5097 38 48 38C21.4903 38 0 29.4934 0 19C0 8.50659 21.4903 0 48 0C74.5097 0 96 8.50659 96 19Z"
        viewBox={{ minX: 0, minY: 0, width: 96, height: 38 }}
        color="rgba(174.00000482797623,179.000004529953,183.00000429153442,1)"
        top="12px"
        left="339px"
        width="96px"
        position="absolute"
        height="38px"
        {...getOverrideProps(overrides, "View.Icon[0]")}
      ></Icon>
      <Text
        padding="0px 0px 0px 0px"
        color="rgba(0,0,0,1)"
        textAlign="left"
        display="flex"
        justifyContent="flex-start"
        fontFamily="Roboto"
        top="15px"
        left="27px"
        fontSize="30px"
        lineHeight="35.15625px"
        position="absolute"
        fontWeight="400"
        direction="column"
        children="Title"
        {...getOverrideProps(overrides, "View.Text[0]")}
      ></Text>
      <Text
        padding="0px 0px 0px 0px"
        color="rgba(0,0,0,1)"
        textAlign="left"
        display="flex"
        justifyContent="flex-start"
        fontFamily="Roboto"
        top="89px"
        left="47px"
        fontSize="30px"
        lineHeight="35.15625px"
        position="absolute"
        fontWeight="400"
        direction="column"
        children="text...."
        {...getOverrideProps(overrides, "View.Text[1]")}
      ></Text>
    </View>
  );
}

こちらのコードは上書きしたとしても、
再度amplify pullをすると上書きされてしまうようです。
ドキュメントをみると、上書きされたくないなら、
適当にrenameして管理すればできると書かれていました。
https://docs.amplify.aws/console/uibuilder/override/

Amplify studioでコンポーネントを操作してみる

Amplify studioを利用してPropsを
指定できるようなので試してみました。
例えば、子要素のtextで、labelを与えることで
テキスト表示を変えることができました。 f:id:remmemento:20211204211827p:plain

また、条件によってpropsを変えることもできるようです。
例えば親コンポーネントに与えたプロパティtypeの値がnotificationの場合は backgroundColorを青にする f:id:remmemento:20211204212018p:plain

それ以外(alert)だったら赤にする f:id:remmemento:20211204212041p:plain

といった設定も可能なようでした。

やってみて

正直なところ私はfigmaが初めてで、若干GUI部分の操作に戸惑いました。
ただ、figmaに慣れれば、割と感覚的に
作業できるようになるのかと思いました。
またamplifyはreinvent前にもupdateが多くあったり
amplify studioにも、他にも機能があるようなので、
引き続き使ってみて記事書ければと思います。

Amazon CodeGuruハンズオン(JAWS-UG千葉)に参加しました

f:id:remmemento:20211112004547p:plain Amazon CodeGuruハンズオン(JAWS-UG千葉)に参加しました。
千葉と言いつつも、オンライン開催をされていて全国から参加し放題です。
今回はCodeGuruのハンズオンがあり、
なんとAWS所属の方々もスピーカー参加されておりました。
(しかもBlackbeltと同じkanasugiさん!)
またハンズオンのシナリオが公式のworkshopかと思うほど
きれいで学びになったのでブログでシェアできればと思い書いてみます。

jawsug-chiba.connpass.com

参考資料

ハンズオンの資料公開OKをもらったので
資料のリンクを貼っておきます。 この資料があればハンズオンできますので興味ある人はぜひ。 drive.google.com ハンズオン内で、コピペしたい箇所のテキストはこちら drive.google.com

資料前半の説明部分はBlackbeltに似た説明があるので
併せて聴くと良いと思います。 www.youtube.com

CodeGuru機能概要

今回のハンズオンではCodeGuruの下記2つの機能を使いました。

CodeGuru Reviewer

f:id:remmemento:20211111232449p:plain こちらはソースのレビューを実施し、改善箇所を指摘してくれる機能です。
今回のシナリオではCodeCommitでPRをして、
その際にCodeGuruがレビューをしてくれる流れになっています。
また、今回のハンズオンでは実施していませんが、
既存のGithub等のリポジトリをレビューをかけるようなこともできます。

CodeGuru Profiler

f:id:remmemento:20211111232617p:plain こちらは実稼働しているアプリケーションの実績をもとに、
どのコード部分がリソース消費に影響を与えているかを解析する機能です。
本ハンズオンでは、EC2上でAgentが動作し、 EC2の稼働状況のデータがCodeGuru Profilerに送られ、 その結果を見るシナリオがあります。

ハンズオンの概要

ハンズオンは2部構成で、Part1はCICDのパイプラインを作成します。
cloud9からcode系サービスを利用し、EC2にdeployできる環境を作ります。
こちらは前準備のようなもので、cfnを使って展開します。 f:id:remmemento:20211111230705p:plain

Part2で主役のCodeGuruを利用します。
初期のサンプルソースではCodeGuruから指摘が出るのですが、
その指摘部分を改善修正することで、
より指摘が少なくなる、といった具合で
ハンズオンのシナリオが組まれています。 f:id:remmemento:20211111230733p:plain

CodeGuru Reviewerを使ってみて

ハンズオン用のサンプルソースでは予め指摘が出るようになっていました。
例えば、AWSのベストプラクティスに反する箇所の指摘
(アクセスキー使わないでRole使って)だったり、
f:id:remmemento:20211111233815p:plain

一般的なJavaの指摘をしてくれます。
f:id:remmemento:20211111233843p:plain

指摘内容は理解しやすいよう
ほぼ全てリンクが付与されていました。
AWSの使い方に関する指摘は、AWSの公式ページのリンクが付与されています。

CodeGuru Profilerを使ってみて

Profilerは動作すると、下記のようなステータスが見れます。

f:id:remmemento:20211111234338p:plain リソースの利用状況を可視化でき、
例えば上記でVisualize CPUを押すと下記のような画面に遷移します。

f:id:remmemento:20211111234648p:plain 情報てんこ盛りですが、
libraryのコード(緑色)と自分のコード(青色)をひとめで区別できたり、
推奨事項を確認できたりと
実際の稼働状況から得られる情報が良い感じで集約されています。
便利ですね。

PythonでもCodeGuru Reviewerを使ってみた

ハンズオンは上記のような流れですが、
pythonでもやってみたかったので
ハンズオン終わった後に、CodeGuru Reviewerで
自分のgithub上のpythonのソースをレビューしてみました。

温度感を表すために、下記1個だけ指摘のケースを書きます。 f:id:remmemento:20211112000546p:plain learn moreのリンク先はpythonの公式ドキュメントで下記でした。 docs.python.org 指摘内容としてはget()の第二引数にNoneをわざわざ指定する必要ないよ、といった指摘でした。 修正してPRすると、指摘が解消されていることが確認できました。 このように一般的なベストプラクティスのような部分を指摘してくれるので良いですね。
これに慣れると、人間は業務ロジックのような
本来の部分に集中できるのかなと思います。

修正前

 eventArn = event.get('arn', None)

修正後

 eventArn = event.get('arn')

さいごに

今回ハンズオン実施頂いた主催者の方々、ありがとうございました!

JAWS-UG千葉支部のハンズオン、
いつも細部まで綺麗で、よく勉強させてもらっています、ありがとうございます。
今後も参加させてもらいます!

AWSの基礎を学ぼう Gateway Load Balancerに参加しました

AWSの基礎を学ぼう Gateway Load Balancerに参加しました
AWSの亀田さんが、ほぼ毎週開催されている
いろんなサービスを触ってみる勉強会です。
今週はGateway Load Balancer でした。
awsbasics.connpass.com

作成した環境

こんな感じの構成をハンズオンで構築できました。

f:id:remmemento:20210904152635p:plain
全体図

VPCが4つ存在し若干細々しているので、コメントを付けてみます f:id:remmemento:20210904154153p:plain

  • 左側のApplicationのVPCから通信がスタートするとします
  • Internet Out時にTransit Gatewayを経由し、右側のVPC(security用)に通信します
  • 右側のVPCでsecurityのcheckをします。このcheckをsecurity applianceのEC2が実施しており、
    そこへの負荷分散でGateway Load Balancerが登場します
  • securityのcheckを通過したら上のVPCを通じてinternet outします

最終的な通信のイメージは下記のような流れです。
f:id:remmemento:20210904155735p:plain

cloudformationを見てみる

実際のハンズオンではcloudformation templateをご提供頂きました。
Gateway Load Balancerや内部のネットワーク構成を
どう構築されているかcfnを見てみました。

※実際のハンズオンでは手順とcloudformation templateをご提供頂いたのですが、
全部公開はNGで、部分的には載せて良いですよ言って頂けたので一部だけ載せさせて頂きます。

Gateway Load Balancer

ALBとほぼ同じでした

  GWLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: gwlb-lab
      Type: gateway
      Subnets:
        - !Ref SecurityApplianceSubnet1
        - !Ref SecurityApplianceSubnet2
      Tags:
      - Key: Name
        Value: gwlb-lab

ターゲットグループでEC2を登録しています

  GWLBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: gwlb-target
      Port: 6081
      Protocol: GENEVE
      TargetGroupAttributes:
      - Key: deregistration_delay.timeout_seconds
        Value: 20
      VpcId: !Ref SecurityVPC
      HealthCheckPort: 22
      HealthCheckProtocol: TCP
      TargetType: instance
      Targets:
        - Id: !Ref ApplianceAZ1EC2Instance
        - Id: !Ref ApplianceAZ2EC2Instance
      Tags:
      - Key: Name
        Value: gwlb-target

VPC Endpoint

VPC Endpointを作成しています
ただ、VPC Endpointには直接loadbalancerの紐付けがされていませんでした。

  GWLBEAZ1:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref SecurityVPC
      ServiceName: !GetAtt GWLBESerivceName.Data
      VpcEndpointType: GatewayLoadBalancer
      SubnetIds:
       - !Ref SecurityApplianceSubnet1

  GWLBEAZ2:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref SecurityVPC
      ServiceName: !GetAtt GWLBESerivceName.Data
      VpcEndpointType: GatewayLoadBalancer
      SubnetIds:
       - !Ref SecurityApplianceSubnet2

代わりにVPC endpointサービスでGatewayLoadBalancerと紐付けをしていました。

  GWLBEService:
    Type: AWS::EC2::VPCEndpointService
    Properties:
      GatewayLoadBalancerArns:
        - !Ref GWLB
      AcceptanceRequired: false

そして、VPC endpointサービスと
VPC endopointを紐づけているようでした。
こちらにカスタムリソースが利用されておりました

  GWLBESerivceName:
    DependsOn: GWLBEService
    Type: Custom::DescribeVpcEndpointServiceConfigurations
    Properties:
      ServiceToken: !GetAtt DescribeGWLBEService.Arn
      Input: !Ref GWLBEService

customリソースを利用してvpc endpoitのサービス名を取得しているようです そして、取得したサービス名でVPC endpointとendpoint serviceを
紐づけているようです。

こうすることで2つのAZに存在するVPC endpointを1つのendpoint serviceに
まとめているようでした。
こんな作り方できるのかととても勉強になりました。

 DescribeGWLBEService:
    Type: AWS::Lambda::Function
    Properties:
      Handler: "index.handler"
      Role: !GetAtt
        - DescribeGWLBEServiceLambdaExecutionRole
        - Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import json
          import logging
          import time
          def handler(event, context):
            time.sleep(600)
            logger = logging.getLogger()
            logger.setLevel(logging.INFO)
            responseData = {}
            responseStatus = cfnresponse.FAILED
            logger.info('Received event: {}'.format(json.dumps(event)))
            if event["RequestType"] == "Delete":
              responseStatus = cfnresponse.SUCCESS
              cfnresponse.send(event, context, responseStatus, responseData)
            if event["RequestType"] == "Create":
              try:
                VpceServiceId = event["ResourceProperties"]["Input"]
              except Exception as e:
                logger.info('VPC Endpoint Service Id retrival failure: {}'.format(e))
              try:
                ec2 = boto3.client('ec2')
              except Exception as e:
                logger.info('boto3.client failure: {}'.format(e))
              try:
                response = ec2.describe_vpc_endpoint_service_configurations(
                  Filters=[
                    {
                      'Name': 'service-id',
                      'Values': [VpceServiceId]
                    }
                  ]
                )
              except Exception as e:
                logger.info('ec2.describe_vpc_endpoint_service_configurations fa: {}'.format(e))
              ServiceName = response['ServiceConfigurations'][0]['ServiceName']
              logger.info('service name: {}'.format(ServiceName))
              responseData['Data'] = ServiceName
              responseStatus = cfnresponse.SUCCESS
              cfnresponse.send(event, context, responseStatus, responseData)
      Runtime: python3.7
      Timeout: 900

ありがとうございました!

Gateway load balanerは初めて触ったのですが、
それ以外にも上記のように普段触ることないNW構成を触ることができ
とても勉強になりました
ありがとうございました!

cdkのsolution constructを利用してみた

f:id:remmemento:20210802095610p:plain

cdkのsolution constructが便利だったのでブログに書いてみます。

solution constructとは

AWSの公式ドキュメントに説明がありました。
aws.amazon.com

AWS Solutions Constructs は、2 つ以上の CDK のリソースを組み合わせ、ロギングや暗号化などのベストプラクティスを実装する複数サービスのパターンを提供します。

要はよくあるパターンをAWSが準備してくれているということですね。

本記事で利用したもの

今回はaws-events-rule-snsを利用してみました。
docs.aws.amazon.com

こちらのイメージ図は下記です。
よくある構成です。 f:id:remmemento:20210802095448p:plain

動かしてみる

下記のようにソースをサンプルで書いてみました。
enableEncryptionWithCustomerManagedKeyをfalseにしておくと
kmsをデフォルトキーで利用してくれます。
(私は最初こちらを指定していなくて無駄なkmsを作成してしまいました)

フォルダ構成イメージ

lib
└── aws-events-rule-sns-stack.ts
patterns
├── SecurityhubNotify.ts
└── index.ts

aws-events-rule-sns-stack.ts

import * as cdk from '@aws-cdk/core';
import { EventsRuleToSnsProps, EventsRuleToSns } from "@aws-solutions-constructs/aws-events-rule-sns";

import * as patterns from '../patterns/index'

export class AwsEventsRuleSnsStack extends cdk.Stack {
  public readonly constructStack: EventsRuleToSns;

  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
       
    const patternToNotify = patterns[patterns.patternName]
  
    const envcode = new cdk.CfnParameter(this, "envcode", {
      type: "String",
      description: "envcode",
      default:"test"
    });
      
    const constructStack = new EventsRuleToSns(this, 'eveSns', {
      eventRuleProps: {
        eventPattern: patternToNotify,
        ruleName: `${envcode.valueAsString}-${patterns.patternName}`
      },
      topicProps:{
        topicName:`${envcode.valueAsString}-${patterns.patternName}`
      },
      enableEncryptionWithCustomerManagedKey: false
    });
    this.constructStack=constructStack
  }
}

index.ts

export { SecurityhubNotify } from "./SecurityhubNotify";

export const patternName='SecurityhubNotify' ;

SecurityhubNotify.ts

export const SecurityhubNotify={
    "source": ["aws.securityhub"],
    "detail-type": ["Security Hub Findings - Imported"],
    "detail": {
      "findings": {
        "Compliance": {
          "Status": ["FAILED", "WARNING", "NOT_AVAILABLE"]
        }
      }
    } 
}

package.json(関連部分のみ抜粋しています)

  "dependencies": {
    "@aws-cdk/core": "^1.114.0",
    "@aws-solutions-constructs/aws-events-rule-sns": "^1.114.0",
    "source-map-support": "^0.5.19"
  },

deployとリソース確認

deployします

cdk deploy --parameters envcode=test

events+snssnsポリシーが作成されています。
ポリシーも良い感じでeventsへの許可が記載されています。

 {
      "Sid": "2",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sns:Publish",
      "Resource": "arn:aws:sns:ap-northeast-1:xxxxxx:test-SecurityhubNotify"
    }

触ってみて

感覚的にはeventsのフィルター部分だけ書けば、
events + snsを作成してくれるので便利でした。

他にもlambdaやs3等を絡めたパターンもあるようなので
機会があれば触ってみようと思います。

SAMでgithub actionsを利用する時にIAMユーザを払い出さないで利用したい

f:id:remmemento:20210703211848p:plain
先日こちらでAWS SAMとgithub actionsの連携を試してみました。

cloud-aws-gcp.hateblo.jp

上記利用する際に、IAMユーザとクレデンシャルを払い出す必要がありました。
ただ、諸事情によりIAMユーザを払い出さないで利用したいというケースがありました。
私の環境ではAWS SSOを利用していたため
AWS SSOで一時的なcredentialを取得し、
その一時的なcredentialをgitにsecretとして登録し、
SAMのdeployができないかと考え試してたところ
できたのでブログ化してみました。

設定方法

AWS SSOでcredentialを取得

まずはAWS SSOでcredentialを取得します。 f:id:remmemento:20210721094908p:plain これは、STS等を利用しても取得が可能ですが、
SSOを利用すると1クリックで取得可能なので便利です
上記の「click to... 」の箇所をクリックすればOKです
下記のようなクレデンシャルが取得できると思います。

export AWS_ACCESS_KEY_ID="xxxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="xxxxxxxx"
export AWS_SESSION_TOKEN="xxxxxxxxxxx"

そして取得した一時credentialを
githubのsecretとして設定します。
そのまま貼り付ければOKでした。 下記の例ではEXPORTSとして登録しています。

f:id:remmemento:20210721095106p:plain

github actionsの設定ファイルを下記のように記載し
pushしたところ正常にdeployができました。
変化点は最後のsam deployのところで、SSOのcredentialを設定した上で、
sam deployを実行しています。

on:
  push:
    branches:
      - master
env:
  stackName: aws-xxxxxxx
  s3BucketName: aws-xxxxxxxxx
  deployRegion: ap-southeast-1
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USER }} --password-stdin
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
        with:
          python-version: '3.7'
      - uses: aws-actions/setup-sam@v1
      # - uses: aws-actions/configure-aws-credentials@v1
      #   with:
      #     aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
      #     aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      #     aws-region: ap-northeast-1
      
      # sam build 
      - run: sam build --use-container
      
      # Run Unit tests- Specify unit tests here 
      # - run : pip install -r requirements.txt

      # sam deploy
      - run: ${{ secrets.EXPORTS }} &&  sam deploy --no-confirm-changeset --no-fail-on-empty-changeset --stack-name ${{ env.stackName }} --s3-bucket ${{ env.s3BucketName }} --capabilities CAPABILITY_IAM --region ${{ env.deployRegion }}

使ってみて

上記は一時的なcredentialなので、時間が切れば権限が切れます。
デプロイする都度上記の設定するのは面倒ですが、
一時的なcredentialの時間制限は最大12時間なので、
一回払い出して設定すれば、その日ぐらいは使いまわせると思います。
たまにしか更新しないものであれば、払い出すIAMの管理も面倒だし、
このやり方も一つかなと思いました。
もっと良いやり方があるという方がいらっしゃたら教えて頂けますと嬉しいです。