20200218のAWSに関する記事は16件です。

AWS 認定 DevOps エンジニア - プロフェッショナル に合格したので勉強したことなどを書く

3行

  • アソシエイト3種には合格しておこう
  • 模試とWebトレーニングを受けよう
  • ユーザー・開発者ガイドを読んで実践しよう

はじめに

AWS 認定 DevOps エンジニア - プロフェッショナル (以下DOP)に合格してAWS認定プロフェッショナル2冠となりました。AWS認定自体は2018年春あたりで当時関わっていたプロジェクトの顧客側チームが全員ソリューションアーキテクト(アソシエイト)保持者だったというところから、自分でもやってみるかということでそれを取得してみるかという動機で取り始めてみたのですが、それから2年以内で達成したということで割と達成感はあります。とは言え、ここまで来てようやく「一端のAWSエンジニアとしてやっていけるスタートラインに立った」という感覚であり、まだまだ「AWS完全に理解した(AWSが提供する基本的なチュートリアルプログラムを完了した)」というのが正直なところでもあります。
本記事ではAWS認定基本5種の中でも取り分けAWS環境のビルド・運用について深い知見を求められるDOP認定試験について、私が勉強した事柄について書いています。

アソシエイト3種には合格しておこう

以前はプロフェッショナルグレードの認定試験を受ける前提条件として、

  • ソリューションアーキテクト(プロフェッショナル)では、ソリューションアーキテクト(アソシエイト)の保持
  • DevOpsエンジニア(プロフェッショナル)では、デベロッパー(アソシエイト)またはSysOpsアドミニストレーター(アソシエイト)の保持

がありましたが、2019年春あたりからこの制限は無くなっています。なので認定の非保持者であってもいきなりDOPを受験できますが、後述する模擬試験で80%以上の成績が出ないようであれば、素直にアソシエイト認定から埋めていくことをお勧めします。「このレベルは理解できている」という自信にもなります。
私の場合は先にソリューションアーキテクト(プロフェッショナル)に合格していたことが、心理的にかなり助けになっていたように思います。

模試を受けよう

AWS トレーニングと認定 では公式の模擬試験が提供されており、本試験と同様に日本語で受験することができます。ただし一度受けると、その後半年間くらいは再度受験することはできません。問題の形式や配分は本試験と同様であるため、自分の理解度合を評価するためにも必ず受けておきましょう。よく言われていることですが、模擬試験の問題および解答についてはスクリーンショットなどで保存しておき、後で必ず吟味を行ないましょう。
なお後述するWebトレーニング内でも言及されていたことですが、模擬試験は本試験よりも問題の難度が高く設定されているようです。私は1回目は55%、2回目は(ほとんど同じ問題で解答の吟味もしていたにも関わらず)70%でした……。

Webトレーニングを受けよう

AWSのEラーニングプログラムには、認定受験者向けの講座が "Exam Readiness" という名前で登録されており、DOP向けのものもあります。

認定アカウント所有者であれば無料で受講できるため、必ず受けておきましょう。試験対策に重点が置かれた内容であり、「こういう状況のときには 認定試験では 特にこう考える」といったことが示されているため、これを受講したかしていないかで認定試験に対する有利さがかなり変わってきます(もっともAWSはアップデートが速いため、認定試験の問題内容が現在の実装に追い付いていないケースもあったりするわけですが……)。
2020年2月段階では英語の講座に日本語字幕、日本語化されたスライドが表示されるもので、英語能力に問題がない場合は英語版を受けることをお勧めします。

クラスルームトレーニングは?

AWSが提供するクラスルームトレーニングとして DevOps Engineering on AWS というものが用意されています。これは「DevOpsの概念およびプラクティスを学習し、それをAWSを用いて実践する」ことを主題としたトレーニングであり、受講者のスキルアップを目的としています。3日間でAWSでのDevOpsに関する実践的な知識と技術を身に付けられる良いトレーニングですが、いかんせん値段が高いため、会社などからの支援が無い場合は受講が難しいかとも思います(APNセレクトティア以上のパートナーであれば2割引。私は幸いにもタイミングがよく会社支援で受講しました)。日頃から実務などでAWSの各種ツールを活用しDevOpsを実践している人であれば、必要性は薄いかもしれません。

ユーザー・開発者ガイドを読んで実践しよう

DOP試験はかなり深くAWSマネージドサービスの仕様に突っ込んだ問題が多く「サービスの組み合わせは合っているけどそのサービスってその仕様じゃないよね?」という気持ちになることが多々あります。模試の結果から苦手とする部分については、ユーザー・開発者ガイドを読んで実際に試してみることが重要です。私は以下のガイドを重点的に確認しました。他、大体のものにおいてモニタリングとトラブルシューティングの項目は読んで触って確認しておくとよいでしょう。

  • EC2 Auto Scaling ※特にスケーリング、モニタリング、トラブルシューティング
  • CloudWatch Events ※特にターゲットと "Events From Supported Service"
  • Elastic Beanstalk ※特に環境の管理、設定、モニタリング、AWSサービスの統合、トラブルシューティング
  • CodeDeploy ※特にモニタリング、トラブルシューティング
  • CodePipeline ※特に製品とサービスの統合、モニタリング、トラブルシューティング
  • CloudFormation ※特にヘルパースクリプト、トラブルシューティング

またDOP認定の紹介ページにある「試験ガイド」で示されているホワイトペーパー各種(英語のみ)を一読しておくことも重要です。各資料とも概論的なところは習熟レベル次第では読み飛ばしても構いませんが、それらをAWSのマネージドサービス群を組み合わせてどのように実現するのかという部分は、アーキテクチャ図と併せて確認しておくのがよいでしょう。
あとクラウドネイティブなアプリケーションの理解に乏しい場合は The Twelve-Factor App (日本語訳) について復習しておきましょう。

本試験

180分間、張り付きで試験問題を解答します(最初のガイダンスと最後のアンケートを含めるともう少し掛かります。それらは試験時間に含まれません)。月並ですが前日はよく寝て、試験前にはトイレを済ましておきましょう。
まず問題番号表示を最後まで送って、設問の総数を把握し、解答に何分、見直しに何分……と試験時間を配分します。解答を進めながら、10問を目安に残り時間を確認し、1問あたりの解答時間が最初の見積とどれだけずれているかを確認します。半分解いたら1分程度休憩、全問解いたら3分程度休憩し、フラグを立てた問題を再検討します。なお3時間と長い試験ですが、問題分量に対して決して充分な時間が用意されているわけではなく、全問を見直す時間は残りませんでした。フラグを立てなかった問題については自分の解答を信じるしかありません。
試験の終了後にアンケートへ回答し、その後で結果が表示されます。
「おめでとうございます。」
と表示されたときの喜びや安堵は長い試験であることもあり格別ですが、他の受験者に配慮して退出後に喜びましょう。
なお結果のAWS認定アカウントへの配信は、早くて翌日の18時以降くらいです(メールでの通知はさらに遅い)。やきもきもしますが、割り切って待ちましょう。
ちなみに設問自体が結構おもしろく、実運用でも取り入れてみようと思えるものも少なからずあり、認定試験そのものについての満足度は結構高めでした。

おわりに

認定スクラムマスター(CSM)に合格したので試験合格までの流れを紹介する という記事に印象的な文章があったので引用します。

そして、研修の最後に認定スクラムトレーナーの方が以下のようなことをおっしゃっていたのがとても印象的だった。

認定スクラムマスターはスクラムの知識があることを証明するだけです。
あなた方は今、スクラム実践のスタートラインに立ちました。
ここからが本当の戦いなのです。

本当にこの通りだと思います。どんな認定も資格も、その知識を備えたプロフェッショナルとして活動していくための門をくぐり抜けただけにすぎません。私の関わったシステムやサービスが少しでもいいものとなるよう、精進していきたいと思います。
それでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon AppStream 2.0の基礎

AppStreamは記事やマニュアル、blackbeltも詳細が乏しいので記載してみました。

2種類のWindowsインスタンス

image builderインスタンス イメージをビルドするためだけに存在するインスタンスです。ビルドが終わったら役割を終えます
fleetインスタンス ビルドしたイメージを実際の配信に使う、auto scaling可能なインスタンスです。1配信1インスタンスのはず。

ビルドするための3つのユーザー

image builderで利用します。

template user

配信したいアプリケーションのセップアップをするユーザーです
このユーザー上で設定したものが新しいユーザーにコピーされ、配信されます
Windowsユーザー名:DefaultProfileUser

administrator user

用途は2つ。

  • イメージをビルドするツールで実際にビルドすること
  • template userが使うようなOS全体の設定を行うこと

例えばOSの地域設定やlocal group policyの変更など。
こみいったことをする場合Windowsの知識が必要です

Windowsユーザー名:ImageBuilderAdmin

test user

template userの設定を検証できるユーザーです
しかし残念ながら配信時の完全に同一な環境ではありません
例えば配信時に実行されるsession scriptは実行されませんし、ホームフォルダの場所も異なります

Windowsユーザー名:ImageBuilderTest

作ったイメージはどこに行くか

Images -> Image Registoryにあります
GUIがわかりにくいのですが、絞り込み可能です

session script

session scriptというもので配信時にスクリプトを実行できます。
image builderインスタンスの中にC:\AppStream\SessionScripts\config.jsonがあるので、administratorユーザーで修正しましょう。
実行ログはs3に出力されます。
https://docs.aws.amazon.com/ja_jp/appstream2/latest/developerguide/use-session-scripts.html
検証時は管理者権限でメモ帳を開いて修正しました。

配信アプリを実行するWindowsユーザー

PhotonUserというWindowsユーザーです。
デスクトップなどもC:¥PhotonUser配下にあります

残る課題

開発サイクル

純粋に時間がかかります
- ビルド 40分ぐらい
- fleet 起動10分ぐらい
- 初回セッション 2,3分ぐらい
また、単体のデバッグ機能に乏しいので、外部へのメトリクスやログ送信があると良さそうです。
セッション終了後もしばらくセッションが残るところも少し困ります

セキュリティ

まだ発展途上にみえます。

sampleのFirefoxからフォルダを開いてみた事例です
https://aws.amazon.com/jp/appstream2/try-sample-applications/
メモ帳も一瞬開かれた後消えます。様々なコマンドも実行できそうです
image.png

不特定多数への配信はあまり向かず、特定のある程度信頼できるユーザーへの配信なら有効活用できそうです
デモ環境など。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda内でpythonを使用してCloudWatchに新規のロググループを生成する方法

概要

AWSのCloudWatchに新しいロググループをつくる。
その処理をLambdaでできないかを検証してみた。

単純にロググループを生成する
make
import boto3

logs = boto3.client('logs')
response = logs.create_log_group(
 logGroupName = "log/group/name"
)
ログの保持期間を付与
make
response = logs.put_retention_policy(
# 付与したいロググループ名
  logGroupName = "log/group/name",
# 保存期間(日)を設定
  retentionInDays = 14
)

参考にしたサイト

Boto 3 DocumentationのCloudWatchLogs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

lambda内でパラメータストアから値を取得する方法(python使用)

概要

AWSのlambdaでパラメータストアの値を取得する方法をまとめる。

実際のコード

get
# 1. boto3をインポート
import boto3

ssm = boto3.client('ssm')
# 2. get_parameterメソッドでパラメータを取得
response = ssm.get_parameter(

# 3. パラストに登録されているキー名を設定
            Name = '/add/company-codes',
# 4. 値のみ取得したい場合はWithDecryption = False
   # 全ての情報を取得したい場合はWithDecryption = True
            WithDecryption=False
        )
value = response['Parameter']['Value']

boto3で使用できるメソッドについて

ここにいくつかのメソッドが載っています↓
(上で使用しているget_parameterも)

Boto 3 Documentation

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python内でパラメータストアから値を取得する方法

概要

AWSのlambdaでパラメータストアの値を取得する方法をまとめる。

実際のコード

get
# 1. boto3をインポート
import boto3

ssm = boto3.client('ssm')
# 2. get_parameterメソッドでパラメータを取得
response = ssm.get_parameter(

# 3. パラストに登録されているキー名を設定
            Name = '/add/company-codes',
# 4. 値のみ取得したい場合はWithDecryption = False
   # 全ての情報を取得したい場合はWithDecryption = True
            WithDecryption=False
        )
value = response['Parameter']['Value']

boto3で使用できるメソッドについて

ここにいくつかのメソッドが載っています↓
(上で使用しているget_parameterも)

Boto 3 Documentation

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Cognitoを利用してLaravelアプリに認証機能を実装する

artisan コマンドで簡単に認証機能を実装できるLaravelですが、認証情報の管理は自DBで行うことになります。
今回はよりセキュリティを高めるために、AWS Cognitoに認証情報と機能を切り出した構成で実装してみることにします。

ですが 公式ドキュメント では主に、iOS/Androidアプリやjavascriptアプリへ組み込む例が紹介されています。
AWS SDK for PHP も用意されてはいるのですが、有用な日本語情報がかなり少なかったので概要を紹介したいと思います。

AWS Cognito とは

AWSが提供するアクセスコントロールサービスです。
IDプロバイダとして認証情報を保管し、トークンを払い出してくれるものです。

Amazon Cognito は、ウェブアプリケーションやモバイルアプリケーションの認証、許可、ユーザー管理をサポートしています。ユーザーは、ユーザー名とパスワードを使用して直接サインインするか、Facebook、Amazon、Google などのサードパーティーを通じてサインインできます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/what-is-amazon-cognito.html

Cognitoには以下の2種類の管理プールがあります。

ユーザープール

ユーザプールは Amazon Cognito のユーザディレクトリです。ユーザープールを使用すると、ユーザーは Amazon Cognito を通じてウェブまたはモバイルアプリにログインできます。また、ユーザーは Google、Facebook、Amazon などのソーシャル ID プロバイダー、および SAML ベースの ID プロバイダー経由でユーザープールにサインインすることもできます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools.html

IDプール

Amazon Cognito ID プール (フェデレーテッドアイデンティティ) では、ユーザーの一意の ID を作成し、ID プロバイダーで連携させることができます。ID プールを使用すると、権限が制限された一時的な AWS 認証情報を取得して、他の AWS サービスにアクセスできます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-identity.html

今回はWebアプリへのログインに使用したいので、 ユーザープール を使用します。

手順

LaravelアプリにCognitoを使用したログイン機能を実装するために、以下の流れで作業していきます。

  1. Cognitoユーザープールを作成
  2. AWS SDK for PHPをインストール
  3. Laravelアプリ内にCognitoとの連携を実装

1. Cognitoユーザープールを作成

まずは認証情報を管理する場所を作ります。
こちら を参考にしてください。

外部のアプリケーションからCognitoへリクエストするためにトークン(アプリクライアントID/シークレット)を払い出す必要があります。
「アプリクライアント」を作成し、以下のように設定します。
ユーザープール - アプリクライアント

  • 作成時に "クライアントシークレットを生成" に✅を入れて、アプリクライアントのシークレットも生成します
  • 今回はバックエンドアプリからのリクエストなので "認証用の管理 API のユーザー名パスワード認証を有効にする (ALLOW_ADMIN_USER_PASSWORD_AUTH)" に✅を入れて ADMIN_USER_PASSWORD_AUTH の認証フローを使用できるようにしておきます。

参考:
? ユーザープールのアプリクライアントの設定
? ユーザープール認証フロー - サーバー側の認証フロー

2. AWS SDK for PHPをインストール

PHPで実装するためにSDKをインストールします。
こちら を参考にしてください。

3. Laravelアプリ内にCognitoとの連携を実装

ここからが本題です?‍?
今回は以下のようにして認証機能を実装しました。

  1. Cognitoへ認証情報(ID/password)をリクエスト
  2. 認証に成功したら、払い出されたIDトークンとリフレッシュトークンをCookieに保存
  3. middlewareでIDトークンを検証
  4. IDトークンが正しいと検証できればログインが必要なページを表示、または処理を実行する

実装

CognitoユーザープールAPI には接頭辞 "Admin" がついたものがいくつか用意されています。
これらは管理者として操作できるもので、インスタンス化する際にcredentials(IAMユーザー情報)が必要です。

1. Cognitoへ認証情報(ID/password)をリクエスト

こちらの記事 を参考にさせていただきました?‍♀️

まずはじめに、 adminInitiateAuth で認証情報をCognitoへリクエストします。
今回アカウントはユーザーがサインアップするのではなく、管理者が発行する仕様なのでいきなりここからです。
"AuthFlow" には ADMIN_NO_SRP_AUTH を指定します。(最新では ADMIN_USER_PASSWORD_AUTH に置き換わったようです)

CognitoService.php
namespace App\Services;

use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
use Aws\CognitoIdentityProvider\Exception\CognitoIdentityProviderException;

class CognitoService
{
  private $library_version;
  private $region;
  private $access_key;
  private $secret_key;
  private $user_pool_id;
  private $client_id;
  private $client_secret;
  private $COGNITO_CLIENT;

  public function __construct()
  {
    $this->library_version = config('auth.aws.library_version');
    $this->region          = config('auth.aws.default_region');
    $this->access_key      = config('auth.aws.access_key');
    $this->secret_key      = config('auth.aws.secret_key');
    $this->user_pool_id    = config('auth.cognito.user_pool_id');
    $this->client_id       = config('auth.cognito.client_id');
    $this->client_secret   = config('auth.cognito.client_secret');
  }

  private function adminInstantiation(): void
  {
      $this->COGNITO_CLIENT = new CognitoIdentityProviderClient(
          [
              'version'     => $this->library_version,
              'region'      => $this->region,
              'credentials' => [
                  'key'    => $this->access_key,
                  'secret' => $this->secret_key,
              ],
          ]
      );
  }

  /**
   * ユーザー認証
   *
   * @param  string  $username
   * @param  string  $password
   *
   * @return array
   */
  public function authenticate(string $username, string $password): array
  {
      try {
          $this->adminInstantiation();
          $response = $this->COGNITO_CLIENT->adminInitiateAuth(
              [
                  'AuthFlow'       => 'ADMIN_NO_SRP_AUTH',
                  'AuthParameters' => [
                      'USERNAME'    => $username,
                      'PASSWORD'    => $password,
                      'SECRET_HASH' => self::cognitoSecretHash($username),
                  ],
                  'ClientId'       => $this->client_id,
                  'UserPoolId'     => $this->user_pool_id,
              ]
          );
      } catch (InvalidArgumentException $e) {
          return [
              'result'  => false,
              'message' => __('validation.custom.auth_id.form'),
          ];
      } catch (CognitoIdentityProviderException $exception) {
          $errorCode = $exception->getAwsErrorCode();

          // エラーメッセージ
          $errorMessage = __('auth.other');
          if ($errorCode === self::NOT_AUTHORIZED) {
              if (strpos($exception->getAwsErrorMessage(), 'exceeded') == false) {
                  $errorMessage = __('auth.incorrect');
              } else {
                  // 試行回数超過
                  $errorMessage = __('auth.exceeded');
              }
          } elseif ($errorCode === self::TOO_MANY_REQUESTS) {
              $errorMessage = __('auth.too_many');
          } else {
              // 上記以外のエラーコードの場合
          }

          return [
              'result'  => false,
              'message' => $errorMessage,
          ];
      }

      return [
          'result' => true,
          'data'   => $response->toArray(),
      ];
  }

  protected function cognitoSecretHash($username)
  {
    return $this->hash($username.$this->client_id);
  }

  protected function hash($message)
  {
    $hash = hash_hmac(
      'sha256',
      $message,
      $this->client_secret,
      true
    );

    return base64_encode($hash);
  }
}

adminInitiateAuth のリクエスト項目にある SECRET_HASH の詳細については以下を参照してください。

参考:
ユーザーアカウントのサインアップと確認 - SecretHash 値の計算

2. 認証に成功したら、払い出されたIDトークンとリフレッシュトークンをCookieに保存

adminInitiateAuth で無事認証されると、Cognitoから3種類のトークンが返されます。

Amazon Cognito ユーザープールには、OpenID Connect (OIDC) オープン標準で定義されている ID トークン、アクセストークン、および更新トークンが実装されています。
・ID トークンには、name、email、phone_number といった、認証されたユーザーの ID に関するクレームが含まれます。
・アクセストークンは、スコープとグループを含み、承認されたリソースへのアクセスを許可するために使用されます。
・更新トークンには、新しい ID またはアクセストークンの取得に必要な情報が含まれます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html

このうち IDトークンリフレッシュトークン をCookieに保存します。

3. middlewareでIDトークンを検証

ユーザーが先ほどのIDトークンを伴ってリクエストしてきたときに、それが正しいか検証することでログイン状態であると判断します。
モバイルアプリ向けのSDKなどではこのへんをいい感じに使いやすくしてくれているようですが、for PHPではそうでなかったので理解するのに苦労しました。

ドキュメント を参考に、IDトークン(JWT)をデコードして検証するのに以下のライブラリを使用しました。

この検証処理をmiddlewareから呼び出すようにします。

4. IDトークンが正しいと検証できればログインが必要なページを表示、または処理を実行する

middlewareで先ほどのIDトークン検証処理を実行し、検証に成功した場合は return $next($request); 、失敗した場合はログイン画面にリダイレクトさせます。

まとめ

そもそもCognitoってなんか便利そう!使えそう!ぐらいのテンションで採用しましたが、ふたを開けてみるとOpenID Connectの仕組みをちゃんと理解できていなくて、まずそこから始まりました?
OpenID Connectについてはこちらを参考にさせていただきました?‍♀️
一番分かりやすい OpenID Connect の説明

また、Laravel + Cognitoで実装しようとした当時(2019年5月ごろ)は "cognito laravel" なんかでぐぐっても情報がほとんどありませんでした。
あったとしてもサインアップまでのものが多く、そのあとアプリケーション側でどう認証するの?というところで悩みました。

いま検索してみると、とても素敵な記事を見つけることができましたのでリンクさせていただきます。
こちらではちゃんとLaravelのGuardを利用するかたちで実装されています。
わたしはLaravelへの造詣もそこまで深くなかったので、この方法には至れませんでした。。

Laravel + AWS Cognito での認証機能の実装【ユーザー新規登録編】
Laravelのユーザ認証をAmazon Cognitoで行う方法

結果的にCognitoで認証の情報管理、および処理を賄うことができたのは、実装面でも運用面でもメリットが多かったと感じています。
メールアドレス登録時に検証コードを送信してくれるのはありがたいですし、管理画面上でユーザーの検索、無効化なども簡単にできます。
一度仕組みを理解さえしてしまえば、だいぶ簡単にログイン機能を導入できるなという感じです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Configの初期設定とマネージドルールを Terraform で設定して、非準拠項目を確認して対処してみる

1. はじめに

  • AWS Config の初期設定を Terraform (v0.12.9) を使って設定する。
  • ついでに、AWS Config マネージドルールを全く設定していなかったので、これも Terraform で設定して、現状の設定が AWS推奨のベストプラクティスに準拠しているかを確認してみる。
  • 非準拠対応をしてみる。

2. AWS Config 設定の目的

AWS Config の「記録をオン」に設定することで、指定したリソースの変更履歴やイベント履歴のデータを S3 へ格納するす(デフォルトの期間は7年)。このデータを利用して、例えば次のことができるようになる。

  • 「ダッシュボード」を使うと、履歴データを可視化してリソースの関係性を確認したり、設定変更やイベント発生を時系列で確認したりできる。
    ⇒ 簡単なトラブルシューティング、リソースの変更内容確認など。

  • 「高度なクエリ」を使うと、履歴データへクエリを発行して詳細な分析ができる。
    ⇒ 高度なトラブルシューティング、セキュリティ分析など。

  • 「Configルール」を使うと、リソースがAWS推奨のベストプラクティスに準拠しているかや、各企業独自のコンプライアンスに違反していないかを評価することができる。
    ⇒ セキュリティチェック、ベストプラクティス準拠チェック、コンプライアンス違反チェックなど。

参考
AWS Docs: AWS Configとは
AWS Docs: AWS Config の概念
AWS Docs: AWS Config の仕組み

3. AWS Config の初期設定を Terraform (v0.12.9) を使って設定する

3.1. Terraform構成

anyservice/
└── anyenv
    ├── awsconfig
    │   ├── backend.tf
    │   ├── main.tf
    │   ├── provider.tf -> ../../../provider.tf
    │   └── terraform_remote_state.tf -> ../../../terraform_remote_state.tf
    ├── iam
    │   ├── aws-config.tf
    │   ├── backend.tf
    │   ├── provider.tf -> ../../../provider.tf
    │   └── terraform_remote_state.tf -> ../../../terraform_remote_state.tf
    └── s3
        ├── awsconfig.tf
        ├── backend.tf
        └── provider.tf -> ../../../provider.tf

3.2. provider.tf, credentials

  • provider.tf
provider.tf
provider "aws" {
  version                 = "2.48.0"
  shared_credentials_file = "../../../credentials"
  profile                 = "terraform"
  region                  = "ap-northeast-1"
}
  • credentials
[terraform]
aws_access_key_id = チョメチョメ
aws_secret_access_key = チョメチョメ

3.3. 履歴データを格納する S3 バケットを作成

  • 作成概要

    • バケット名: awsconfig-AWSアカウントID
    • リージョン: ap-northeast-1
    • バージョニング: 有効
    • バケットポリシー設定
  • anyservice/anyenv/s3/backend.tf

backend.tf
terraform {
  required_version = ">= 0.12.9"
  backend "s3" {
    shared_credentials_file = "../../../credentials"
    profile                 = "terraform"
    region                  = "ap-northeast-1"
    bucket                  = "terraform-tfstate-チョメチョメ"
    key                     = "anyservice/anyenv/s3/terraform.tfstate"
  }
}
  • anyservice/anyenv/s3/awsconfig.tf
awsconfig.tf
data "aws_caller_identity" "current" {
}
resource "aws_s3_bucket" "awsconfig" {
  bucket        = "awsconfig-${data.aws_caller_identity.current.account_id}"
  acl           = "private"
  force_destroy = "false"
  region        = "ap-northeast-1"
  versioning {
    enabled = true
  }
}
data "aws_iam_policy_document" "s3bucket_policy-awsconfig" {
  version = "2012-10-17"
  statement {
    sid    = "AWSConfigBucketPermissionsCheck"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "s3:GetBucketAcl"
    ]
    resources = [
      "${aws_s3_bucket.awsconfig.arn}"
    ]
  }
  statement {
    sid    = "AWSConfigBucketDelivery"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "s3:PutObject"
    ]
    resources = [
      "${aws_s3_bucket.awsconfig.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/Config/*"
    ]
    condition {
      test     = "StringEquals"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
  }
}
resource "aws_s3_bucket_policy" "awsconfig" {
  bucket = "${aws_s3_bucket.awsconfig.bucket}"
  policy = "${data.aws_iam_policy_document.s3bucket_policy-awsconfig.json}"
}
output "awsconfig_arn" {
  value = aws_s3_bucket.awsconfig.arn
}
output "awsconfig_name" {
  value = aws_s3_bucket.awsconfig.id
}
  • terraform_remote_state.tf
terraform_remote_state.tf
data "terraform_remote_state" "S3" {
  backend = "s3"
  config = {
    shared_credentials_file = "../../../credentials"
    profile                 = "terraform"
    region                  = "ap-northeast-1"
    bucket                  = "terraform-tfstate-チョメチョメ"
    key                     = "anyservice/anyenv/s3/terraform.tfstate"
  }
}

3.4. IAMポリシー、ロールを作成する

  • 作成概要

    • IAMポリシー名: awsconfig
    • IAMロール名: awsconfig
    • 信頼されたエンティティ: config.amazonaws.com
    • アタッチするポリシー: AWSConfigRole(AWS 管理ポリシー), awsconfig(管理ポリシー)
  • anyservice/anyenv/iam/backend.tf

backend.tf
terraform {
  required_version = ">= 0.12.9"
  backend "s3" {
    shared_credentials_file = "../../../credentials"
    profile                 = "terraform"
    region                  = "ap-northeast-1"
    bucket                  = "terraform-tfstate-チョメチョメ"
    key                     = "anyservice/anyenv/iam/terraform.tfstate"
  }
}
  • anyservice/anyenv/iam/awsconfig.tf
awsconfig.tf
data "aws_caller_identity" "current" {
}
data "aws_iam_policy_document" "assume_role_policy-awsconfig" {
  version = "2012-10-17"
  statement {
    sid    = ""
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["config.amazonaws.com"]
    }
    actions = [
      "sts:AssumeRole"
    ]
  }
}
resource "aws_iam_role" "role_awsconfig" {
  name               = "awsconfig"
  assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy-awsconfig.json}"
}
resource "aws_iam_role_policy_attachment" "awsconfig_AWSConfigRole" {
  role       = aws_iam_role.role_awsconfig.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSConfigRole"
}
data "aws_iam_policy_document" "iam_policy-awsconfig" {
  version = "2012-10-17"
  statement {
    effect = "Allow"
    actions = [
      "s3:PutObject*"
    ]
    resources = [
      "${data.terraform_remote_state.S3.outputs.awsconfig_arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
    ]
    condition {
      test     = "StringLike"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
  }
  statement {
    effect = "Allow"
    actions = [
      "s3:GetBucketAcl"
    ]
    resources = [
      data.terraform_remote_state.S3.outputs.awsconfig_arn
    ]
  }
}
resource "aws_iam_policy" "iam_policy-awsconfig" {
  name   = "awsconfig"
  policy = "${data.aws_iam_policy_document.iam_policy-awsconfig.json}"
}
resource "aws_iam_role_policy_attachment" "awsconfig" {
  role       = aws_iam_role.role_awsconfig.name
  policy_arn = "${aws_iam_policy.iam_policy-awsconfig.arn}"
}
output "role_awsconfig_arn" {
  value = aws_iam_role.role_awsconfig.arn
}
  • terraform_remote_state.tf
terraform_remote_state.tf
data "terraform_remote_state" "IAM" {
  backend = "s3"
  config = {
    shared_credentials_file = "../../../credentials"
    profile                 = "terraform"
    region                  = "ap-northeast-1"
    bucket                  = "terraform-tfstate-チョメチョメ"
    key                     = "anyservice/anyenv/iam/terraform.tfstate"
  }
}

3.5. AWS Config を作成する

  • 作成概要

    • 名前: awsconfig-AWSアカウントID
    • AWS Config ロール名: awsconfig
    • 記録するリソースタイプ
      • このリージョンでサポートされているすべてのリソースを記録する
      • グローバルリソース (AWS IAM リソースなど) を含める
    • 配信方法:
      • S3バケット: awsconfig-アカウントID
      • SNS トピック名: - (今回は AWS Config から SNS への通知設定はしない)
    • データ保持期間: デフォルト(7年)
  • anyservice/anyenv/awsconfig/backend.tf

backend.tf
terraform {
  required_version = ">= 0.12.9"
  backend "s3" {
    shared_credentials_file = "../../../credentials"
    profile                 = "terraform"
    region                  = "ap-northeast-1"
    bucket                  = "terraform-tfstate-チョメチョメ"
    key                     = "anyservice/anyenv/awsconfig/terraform.tfstate"
  }
}
  • anyservice/anyenv/awsconfig/main.tf
main.tf
data "aws_caller_identity" "current" {
}
resource "aws_config_configuration_recorder" "awsconfig" {
  name     = "awsconfig-${data.aws_caller_identity.current.account_id}"
  role_arn = data.terraform_remote_state.IAM.outputs.role_awsconfig_arn
  recording_group {
    all_supported                 = "true"
    include_global_resource_types = "true"
  }
}
resource "aws_config_delivery_channel" "awsconfig" {
  name           = "awsconfig-${data.aws_caller_identity.current.account_id}"
  s3_bucket_name = data.terraform_remote_state.S3.outputs.awsconfig_name
  depends_on     = ["aws_config_configuration_recorder.awsconfig"]
  snapshot_delivery_properties {
    delivery_frequency = "One_Hour"
  }
}
  • AWS Config を作成すると、「記録はオン」となり、履歴データが S3 バケットへ配信される。
20200217-06.png

4. 「3.」で作成した設定の確認

4.1. 履歴データを格納する S3 バケット (awsconfig-AWSアカウントID)のバケットポリシー

20200217-01.png

4.2. IAMポリシー(awsconfig)

20200217-02.png

4.3. IAMロール(awsconfig)

20200217-03.png
20200217-04.png

4.4. AWS Config

20200217-05.png

5. AWS Config ルール

5.1. AWS Config ルールは2通りある

  • マネージドルール(AWS側で作成して提供しているルール)

    • 定義済みのカスタマイズ可能なルールであり、AWS リソースが一般的なベストプラクティスに準拠しているかどうかを評価するために AWS Config で使用します。
    • AWSが提供している AWS Config マネージドルールのリストの中から利用したいものを選んでルールの使用を有効にする。
    • ルールを有効化すると、AWS Config はリソースをルールの条件と比較します。この初期評価後は、評価がトリガーされるたびに AWS Config で評価が実行されます。
    • 評価のトリガーは2タイプある
      • 設定変更 – ルールの範囲に該当するリソースで設定が変更されると、AWS Config によって評価がトリガーされます。
      • 定期的 – 指定した間隔 (24 時間ごとなど) で、AWS Config でのルールの評価が実行されます。
  • カスタムルール(ユーザ側で作成するルール)

    • カスタムルールを作成して AWS Config に追加できます。各カスタムルールは AWS Lambda 関数と関連付けます。この関数には、AWS リソースがルールに準拠しているかどうかを評価するロジックが含まれています。
    • この関数をルールに関連付け、設定変更または定期的な間隔に応じてルールから関数を呼び出します。
    • 次に、関数はリソースがルールに準拠しているかどうかを評価し、評価結果を AWS Config に送信します。

引用
AWS Docs: AWS Config マネージドルール
AWS Docs: AWS Config カスタムルール

5.2. 今回使用するマネージドルール

  • 分析

  • コンピューティング

    • ebs-optimized-instance
      • EBS 最適化できる EC2 インスタンスに対して EBS 最適化が有効になっているかどうかを確認します。
    • ec2-stopped-instance
      • 許可されている日数よりも長く停止しているインスタンスがあるかどうかを確認します。
      • デフォルトは30日。ここでは、60日に設定する。
    • ec2-instance-managed-by-systems-manager
      • アカウントの Amazon EC2 インスタンスが AWS Systems Manager で管理されているかどうかを確認します。
    • ec2-security-group-attached-to-eni
      • セキュリティグループが EC2 インスタンスまたは Elastic Network Interface にアタッチされていることを確認します。
    • ec2-volume-inuse-check
      • EBS ボリュームが EC2 インスタンスにアタッチされているかどうかを確認します。
    • eip-attached
      • VPC に割り当てられたすべての Elastic IP アドレスが、EC2 インスタンスまたは使用中の Elastic Network Interface (ENI) にアタッチされているかどうかを確認します。
    • elb-logging-enabled
      • Application Load Balancer と クラシックロードバランサー でログ記録が有効になっているかどうかを確認します。
    • restricted-ssh
      • セキュリティグループの受信 SSH トラフィックがアクセス可能かどうかを確認します。
  • データベース

    • db-instance-backup-enabled
      • RDS DB インスタンスでバックアップが有効になっているかどうかを確認します。
    • dynamodb-throughput-limit-check
      • DynamoDB プロビジョンドスループットがアカウントの上限に近づいているかどうかを確認します。
    • rds-instance-public-access-check
      • Amazon Relational Database Service インスタンスがパブリックにアクセス可能でないかどうかを確認します。
    • rds-multi-az-support
      • RDS DB インスタンスの高可用性が有効になっているかどうかを確認します。
    • rds-snapshots-public-prohibited
      • Amazon Relational Database Service (Amazon RDS) スナップショットがパブリックかどうかを確認します。
  • マネジメントとガバナンス

    • cloud-trail-cloud-watch-logs-enabled
      • AWS CloudTrail 証跡がログを Amazon CloudWatch Logs に送信するように設定されているかどうかを確認します。
    • cloudtrail-enabled
      • AWS アカウントで AWS CloudTrail が有効になっているかどうかを確認します。
    • cloud-trail-encryption-enabled
      • AWS CloudTrail がサーバー側の暗号化 (SSE) AWS Key Management Service (AWS KMS) カスタマーマスターキー (CMK) 暗号化を使用するよう設定されているかどうかを確認します。
    • cloud-trail-log-file-validation-enabled
      • AWS CloudTrail でログを含むダイジェストファイルを作成するかどうかを確認します。
    • cloudtrail-s3-dataevents-enabled
      • 少なくとも 1 つの AWS CloudTrail 証跡がすべての S3 バケットの Amazon S3 データイベントをログ記録しているかどうかを確認します。
    • multi-region-cloud-trail-enabled
      • マルチリージョン AWS CloudTrail が少なくとも 1 つあることを確認します。
  • ネットワークとコンテンツ配信

    • internet-gateway-authorized-vpc-only
      • インターネットゲートウェイ (IGW) が承認された Amazon Virtual Private Cloud (VPC) にのみアタッチされていることを確認します。
      • authorizedVpcIds パラメータに、IGWのアタッチが許可されたVPCのidをリストする(複数ある場合はカンマ区切り)。
    • vpc-default-security-group-closed
      • いずれの Amazon Virtual Private Cloud (VPC) のデフォルトのセキュリティグループでもインバウンドとアウトバウンドのいずれのトラフィックも許可しないことを確認します。
    • vpc-flow-logs-enabled
      • Amazon Virtual Private Cloud フローログが見つかり、Amazon VPC に対して有効になっているかどうかを確認します。
  • セキュリティ、アイデンティティ、コンプライアンス

    • access-keys-rotated
      • アクティブなアクセスキーが、maxAccessKeyAge で指定された日数内にローテーションされるかどうかを確認します。
      • デフォルトは90日。ここでは180日に設定する。
    • acm-certificate-expiration-check
      • アカウントの ACM 証明書が、指定の日数内に有効期限切れとしてマークされているかどうかを確認します。
      • ここでは14日に設定する。
    • iam-group-has-users-check
      • IAM グループに少なくとも 1 つの IAM ユーザーが存在するかどうかを確認します。
    • iam-password-policy
      • IAM ユーザーのアカウントパスワードポリシーが、指定した要件を満たしているかどうかを確認します。
      • ここではパスワードポリシーを次のとおりに決める。
        • MaxPasswordAge = "90"
        • MinimumPasswordLength = "12"
        • PasswordReusePrevention = "3"
        • RequireLowercaseCharacters = "true"
        • RequireNumbers = "true"
        • RequireSymbols = "true"
        • RequireUppercaseCharacters = "true"
    • iam-role-managed-policy-check
      • ポリシーのリスト内の AWS Identity and Access Management (IAM) ポリシーがすべての AWS ロールにアタッチされていることを確認します。
    • iam-root-access-key-check
      • ルートユーザーアクセスキーがあるかどうかを確認します。ユーザーアクセスキーが存在しない場合、ルールは COMPLIANT です。
    • iam-user-group-membership-check
      • IAM ユーザーが少なくとも 1 つの IAM グループのメンバーであるかどうかを確認します。
    • iam-user-no-policies-check
      • いずれの IAM ユーザーにもポリシーがアタッチされていないことを確認します。IAM ユーザーは、IAM グループまたはロールからアクセス許可を継承する必要があります。
    • iam-user-unused-credentials-check
      • IAMユーザーが、指定した日数以内に使用されたことのないパスワードまたはアクティブなアクセスキーを持っているかどうかを確認します。
      • デフォルトは90日。ここでは180日に設定する。
    • mfa-enabled-for-iam-console-access
      • コンソールパスワードを使用するすべての IAM ユーザーについて MFA が有効になっているかどうかを確認します。す。
    • root-account-mfa-enabled
      • AWS アカウントのユーザーが、ルートの認証情報を使用してサインインする際に MFA デバイスが必要かどうかを確認します。
  • ストレージ

    • ebs-snapshot-public-restorable-check
      • Amazon Elastic Block Store のスナップショットがパブリックで復元可能でないかどうかを確認します。
    • elb-deletion-protection-enabled
      • Elastic Load Balancing で削除保護が有効になっているかどうかを確認します。
    • s3-account-level-public-access-blocks
      • 必要なパブリックアクセスブロック設定がアカウントレベルから設定されているかどうかを確認します。
    • s3-bucket-public-read-prohibited
      • Amazon S3 バケットでパブリック読み取りアクセスが許可されないことを確認します。
    • s3-bucket-public-write-prohibited
      • Amazon S3 バケットでパブリック書き込みアクセスが許可されないことを確認します。

6. AWS Config ルールの設定を Terraform (v0.12.9)を使って設定する

6.1. Terraform構成

「3.1. Terraform構成」に次のファイルを追加

  • AWS Config ルールを設定する terraform

    • anyservice/anyenv/awsconfig/config-rule.tf
  • AWS Config ルールからの通知を Amazon SNS へ送るための CloudWatch Events を設定する terraform

    • anyservice/anyenv/cloudwatchevents
anyservice/
└── anyenv
    ├── awsconfig
    │   ├── backend.tf
    │   ├── config-rule.tf
    │   ├── main.tf
    │   ├── provider.tf -> ../../../provider.tf
    │   └── terraform_remote_state.tf -> ../../../terraform_remote_state.tf
    ├── cloudwatchevents
    │   ├── awsconfig.tf
    │   ├── backend.tf
    │   ├── provider.tf -> ../../../provider.tf
    ├── iam
    │   ├── aws-config.tf
    │   ├── backend.tf
    │   ├── provider.tf -> ../../../provider.tf
    │   └── terraform_remote_state.tf -> ../../../terraform_remote_state.tf
    └── s3
        ├── awsconfig.tf
        ├── backend.tf
        └── provider.tf -> ../../../provider.tf

6.2. AWS Config ルールからの通知を Amazon SNS へ送るための CloudWatch Events を作成する

  • 作成概要

    • CloudWatch イベントルール名: AWSConfig
    • イベントパターン:
      • source: aws.config
      • detail-type: "Config Rules Compliance Change"
    • ターゲット: SNS トピック(適当なものを使うか、新規で作るか。AWS Chatbot にも対応している。)
  • anyservice/anyenv/cloudwatchevents/awsconfig.tf

awsconfig.tf
resource "aws_cloudwatch_event_rule" "awsconfig" {
  name = "AWSConfig"
  event_pattern = jsonencode(
    {
      detail-type = [
        "Config Rules Compliance Change",
      ]
      source = [
        "aws.config",
      ]
    }
  )
}
resource "aws_cloudwatch_event_target" "awsconfig" {
  rule      = "${aws_cloudwatch_event_rule.awsconfig.name}"
  arn       = "SNSトピックの ARN"
}

6.3. AWS Config ルールを作成する

  • 作成概要

    • 作成する Config ルール: 「5.2. 今回使用するマネージドルール」
    • 初回作成後(ルール適用直後)は、SNSに大量に通知が飛んでくる。
  • anyservice/anyenv/awsconfig/config-rule.tf

config-rule.tf
#------------------------------------------------------------
# AWS Config Rule
#------------------------------------------------------------
#
# Analytics
#
resource "aws_config_config_rule" "elasticache-redis-cluster-automatic-backup-check" {
  name = "elasticache-redis-cluster-automatic-backup-check"
  source {
    owner             = "AWS"
    source_identifier = "ELASTICACHE_REDIS_CLUSTER_AUTOMATIC_BACKUP_CHECK"
  }
}
#
# Compute
#
resource "aws_config_config_rule" "ebs-optimized-instance" {
  name = "ebs-optimized-instance"
  source {
    owner             = "AWS"
    source_identifier = "EBS_OPTIMIZED_INSTANCE"
  }
}
resource "aws_config_config_rule" "ec2-stopped-instance" {
  name = "ec2-stopped-instance"
  source {
    owner             = "AWS"
    source_identifier = "EC2_STOPPED_INSTANCE"
  }
  input_parameters = jsonencode(
    {
      AllowedDays = "60"
    }
  )
}
resource "aws_config_config_rule" "ec2-instance-managed-by-systems-manager" {
  name = "ec2-instance-managed-by-systems-manager"
  source {
    owner             = "AWS"
    source_identifier = "EC2_INSTANCE_MANAGED_BY_SSM"
  }
}
resource "aws_config_config_rule" "ec2-security-group-attached-to-eni" {
  name = "ec2-security-group-attached-to-eni"
  source {
    owner             = "AWS"
    source_identifier = "EC2_SECURITY_GROUP_ATTACHED_TO_ENI"
  }
}
resource "aws_config_config_rule" "ec2-volume-inuse-check" {
  name = "ec2-volume-inuse-check"
  source {
    owner             = "AWS"
    source_identifier = "EC2_VOLUME_INUSE_CHECK"
  }
}
resource "aws_config_config_rule" "eip-attached" {
  name = "eip-attached"
  source {
    owner             = "AWS"
    source_identifier = "EIP_ATTACHED"
  }
  scope {
    compliance_resource_types = [
      "AWS::EC2::EIP"
    ]
  }
}
resource "aws_config_config_rule" "elb-logging-enabled" {
  name = "elb-logging-enabled"
  source {
    owner             = "AWS"
    source_identifier = "ELB_LOGGING_ENABLED"
  }
  scope {
    compliance_resource_types = [
      "AWS::ElasticLoadBalancing::LoadBalancer",
      "AWS::ElasticLoadBalancingV2::LoadBalancer"
    ]
  }
}
resource "aws_config_config_rule" "restricted-ssh" {
  name = "restricted-ssh"
  source {
    owner             = "AWS"
    source_identifier = "INCOMING_SSH_DISABLED"
  }
  scope {
    compliance_resource_types = [
      "AWS::EC2::SecurityGroup"
    ]
  }
}
#
# Database
#
resource "aws_config_config_rule" "db-instance-backup-enabled" {
  name = "db-instance-backup-enabled"
  source {
    owner             = "AWS"
    source_identifier = "DB_INSTANCE_BACKUP_ENABLED"
  }
}
resource "aws_config_config_rule" "dynamodb-throughput-limit-check" {
  name = "dynamodb-throughput-limit-check"
  source {
    owner             = "AWS"
    source_identifier = "DYNAMODB_THROUGHPUT_LIMIT_CHECK"
  }
}
resource "aws_config_config_rule" "rds-instance-public-access-check" {
  name = "rds-instance-public-access-check"
  source {
    owner             = "AWS"
    source_identifier = "RDS_INSTANCE_PUBLIC_ACCESS_CHECK"
  }
  scope {
    compliance_resource_types = [
      "AWS::RDS::DBInstance"
    ]
  }
}
resource "aws_config_config_rule" "rds-multi-az-support" {
  name = "rds-multi-az-support"
  source {
    owner             = "AWS"
    source_identifier = "RDS_MULTI_AZ_SUPPORT"
  }
  scope {
    compliance_resource_types = [
      "AWS::RDS::DBInstance"
    ]
  }
}
resource "aws_config_config_rule" "rds-snapshots-public-prohibited" {
  name = "rds-snapshots-public-prohibited"
  source {
    owner             = "AWS"
    source_identifier = "RDS_SNAPSHOTS_PUBLIC_PROHIBITED"
  }
  scope {
    compliance_resource_types = [
      "AWS::RDS::DBInstance"
    ]
  }
}
#
# Management and Governance
#
resource "aws_config_config_rule" "cloud-trail-cloud-watch-logs-enabled" {
  name = "cloud-trail-cloud-watch-logs-enabled"
  source {
    owner             = "AWS"
    source_identifier = "CLOUD_TRAIL_CLOUD_WATCH_LOGS_ENABLED"
  }
}
resource "aws_config_config_rule" "cloudtrail-enabled" {
  name = "cloudtrail-enabled"
  source {
    owner             = "AWS"
    source_identifier = "CLOUD_TRAIL_ENABLED"
  }
  maximum_execution_frequency = "TwentyFour_Hours"
}
resource "aws_config_config_rule" "cloud-trail-encryption-enabled" {
  name = "cloud-trail-encryption-enabled"
  source {
    owner             = "AWS"
    source_identifier = "CLOUD_TRAIL_ENCRYPTION_ENABLED"
  }
}
resource "aws_config_config_rule" "cloud-trail-log-file-validation-enabled" {
  name = "cloud-trail-log-file-validation-enabled"
  source {
    owner             = "AWS"
    source_identifier = "CLOUD_TRAIL_LOG_FILE_VALIDATION_ENABLED"
  }
}
resource "aws_config_config_rule" "cloudtrail-s3-dataevents-enabled" {
  name = "cloudtrail-s3-dataevents-enabled"
  source {
    owner             = "AWS"
    source_identifier = "CLOUDTRAIL_S3_DATAEVENTS_ENABLED"
  }
}
resource "aws_config_config_rule" "multi-region-cloud-trail-enabled" {
  name = "multi-region-cloud-trail-enabled"
  source {
    owner             = "AWS"
    source_identifier = "MULTI_REGION_CLOUD_TRAIL_ENABLED"
  }
}
#
# Network and Content Delivery
#
resource "aws_config_config_rule" "internet-gateway-authorized-vpc-only" {
  name = "internet-gateway-authorized-vpc-only"
  source {
    owner             = "AWS"
    source_identifier = "INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY"
  }
  input_parameters = jsonencode(
    {
      AuthorizedVpcIds = "vpc-チョメチョメ,vpc-チョメチョメ"
    }
  )
}
resource "aws_config_config_rule" "vpc-default-security-group-closed" {
  name = "vpc-default-security-group-closed"
  source {
    owner             = "AWS"
    source_identifier = "VPC_DEFAULT_SECURITY_GROUP_CLOSED"
  }
}
resource "aws_config_config_rule" "vpc-flow-logs-enabled" {
  name = "vpc-flow-logs-enabled"
  source {
    owner             = "AWS"
    source_identifier = "VPC_FLOW_LOGS_ENABLED"
  }
}
#
# Security, Identity & Compliance
#
resource "aws_config_config_rule" "access-keys-rotated" {
  name = "access-keys-rotated"
  source {
    owner             = "AWS"
    source_identifier = "ACCESS_KEYS_ROTATED"
  }
  input_parameters = jsonencode(
    {
      maxAccessKeyAge = "180"
    }
  )
}
resource "aws_config_config_rule" "acm-certificate-expiration-check" {
  name = "acm-certificate-expiration-check"
  source {
    owner             = "AWS"
    source_identifier = "ACM_CERTIFICATE_EXPIRATION_CHECK"
  }
  input_parameters = jsonencode(
    {
      daysToExpiration = "14"
    }
  )
  maximum_execution_frequency = "TwentyFour_Hours"
  scope {
    compliance_resource_types = [
      "AWS::ACM::Certificate",
    ]
  }
}
resource "aws_config_config_rule" "iam-group-has-users-check" {
  name = "iam-group-has-users-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_GROUP_HAS_USERS_CHECK"
  }
}
resource "aws_config_config_rule" "iam-password-policy" {
  name = "iam-password-policy"
  source {
    owner             = "AWS"
     source_identifier = "IAM_PASSWORD_POLICY"
   }
  depends_on  = ["aws_config_configuration_recorder.awsconfig"]
  description = ""
  input_parameters = jsonencode(
    {
      MaxPasswordAge             = "90"
      MinimumPasswordLength      = "12"
      PasswordReusePrevention    = "3"
      RequireLowercaseCharacters = "true"
      RequireNumbers             = "true"
      RequireSymbols             = "true"
      RequireUppercaseCharacters = "true"
    }
  )
  maximum_execution_frequency = "TwentyFour_Hours"
}
resource "aws_config_config_rule" "iam-root-access-key-check" {
  name = "iam-root-access-key-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_ROOT_ACCESS_KEY_CHECK"
  }
}
resource "aws_config_config_rule" "iam-user-group-membership-check" {
  name = "iam-user-group-membership-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_USER_GROUP_MEMBERSHIP_CHECK"
  }
}
resource "aws_config_config_rule" "iam-user-no-policies-check" {
  name = "iam-user-no-policies-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_USER_NO_POLICIES_CHECK"
  }
}
resource "aws_config_config_rule" "iam-user-unused-credentials-check" {
  name = "iam-user-unused-credentials-check"
  source {
    owner             = "AWS"
    source_identifier = "IAM_USER_UNUSED_CREDENTIALS_CHECK"
  }
  input_parameters = jsonencode(
    {
      maxCredentialUsageAge = "180"
    }
  )
}
resource "aws_config_config_rule" "mfa-enabled-for-iam-console-access" {
  name = "mfa-enabled-for-iam-console-access"
  source {
    owner             = "AWS"
    source_identifier = "MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS"
  }
  description                 = ""
  maximum_execution_frequency = "One_Hour"
}
resource "aws_config_config_rule" "root-account-mfa-enabled" {
  name = "root-account-mfa-enabled"
  source {
    owner             = "AWS"
    source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
  }
  maximum_execution_frequency = "TwentyFour_Hours"
}
#
# Storage
#
resource "aws_config_config_rule" "ebs-snapshot-public-restorable-check" {
  name = "ebs-snapshot-public-restorable-check"
  source {
    owner             = "AWS"
    source_identifier = "EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK"
  }
  maximum_execution_frequency = "TwentyFour_Hours"
}
resource "aws_config_config_rule" "elb-deletion-protection-enabled" {
  name = "elb-deletion-protection-enabled"
  source {
    owner             = "AWS"
    source_identifier = "ELB_DELETION_PROTECTION_ENABLED"
  }
}
resource "aws_config_config_rule" "s3-account-level-public-access-blocks" {

  name = "s3-account-level-public-access-blocks"
  source {
    owner             = "AWS"
    source_identifier = "S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS"
  }
}
resource "aws_config_config_rule" "s3-bucket-public-write-prohibited" {
  name = "s3-bucket-public-write-prohibited"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"
  }
  maximum_execution_frequency = "TwentyFour_Hours"
  scope {
    compliance_resource_types = [
      "AWS::S3::Bucket"
    ]
  }
}
resource "aws_config_config_rule" "s3-bucket-public-read-prohibited" {
  name = "s3-bucket-public-read-prohibited"
  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"
  }
  maximum_execution_frequency = "TwentyFour_Hours"
  scope {
    compliance_resource_types = [
      "AWS::S3::Bucket"
    ]
  }
}

7. 「6.」で作成した設定の確認

7.1. CloudWatch イベントルール

20200218-01.png

7.2. AWS Config ルール

20200218-02.png

7.3. AWS Config ルール からの通知

※ AWS Chatbot から Slack へ飛ばしている

  • 準拠の例
20200218-03.png
  • 非準拠の例
20200218-04.png

7.4. AWS Config ダッシュボード

結構赤いw(非準拠多い・・)

20200218-05.png

8. 非準拠項目の確認例

  • access-keys-rotated を見てみる
20200218-06.png


9個の IAMユーザーが access-keys-rotated ルールに準拠していない。。

20200218-07.png


その中の IAMユーザーの dev-test-user を見てみると、「iam-user-group-membership-check」ルールと「iam-user-no-policies-check」ルールにも準拠していない。。

20200218-08.png

コンプライアンスタイムライン なんていうものもある

20200218-09.png

20200218-10.png

9. 非準拠項目の対応例

9.1. iam-user-group-membership-check ルールの非準拠対応

新規にIAMグループ testgroup を作成して、IAMユーザーの dev-test-user をそのグループに属してみる。

しばらく(数十分)すると、Config ルールから通知が届いた

20200218-11.png
  • 上の通知は、iam-user-group-membership-check ルールの通知で、IAMユーザー dev-test-user がIAMグループに属したのでルールに準拠したという通知。

  • 下の通知は、iam-group-has-users-check ルールの通知で、新規に作成した IAMグループ testgroup がIAMユーザー dev-test-user を持っているので、ルールに準拠しているという通知。

9.2. コンプライタイムラインの確認

IAM ユーザー dev-test-user の コンプライアンスタイムライン を確認すると、非準拠ルールが3個から2個に減った。

20200218-12.png

「関係」を見てみる

20200218-13.png

リンク踏むと、IAMユーザー dev-test-user の設定タイムラインページへ飛ぶ
設定タイムラインの「変更」を見ると、IAMグループ testgroup に属したことが確認できる。

20200218-14.png

IAM ユーザー dev-test-user の コンプライアンスタイムライン に戻って、「変更」を見てみる。
コンプライアンスタイムラインの「変更」を見ると、Configルール iam-user-group-membership-check が NON_COMPLIANT から COMPLIANT へ変わったことが確認できる。

20200218-15.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】CloudFormer使ってみた

はじめに

AWSのCloudFormerというβ版のサービスを使ってみたので作成手順を備忘録として記載します

経緯

現所属の社内システムにがAWS環境にあり、新しいサービスを導入しようとしたが、分かりやすい環境構成図がなく全容が見えませんでした

なので、現状を調査するのにいちいちAWS管理コンソールから紐づいているものを確認するのが億劫だな〜と思い、実環境から環境構成図を逆起こししてくれるようなものはないか社内TOPの技術屋さんに聞いてみたところβ版ではあるがCloudFormerというツールがあること教えてもらったので使用してみました

CloudFormerとは

CloudFormerは、アカウントに既に存在するAWSリソースから環境の起動や設定値などを抜き出して、CloudFormationテンプレートを作成するツールです

テンプレート自体は、JSON形式のテキストファイルなのでそのまま使用して全く同じ環境を作成したり、追記や修正することも可能です

CloudFormerはEC2インスタンスが起動するので起動している間は課金されます

主なメリット

  • 既存環境を可視化しやすい
  • 既存環境のリソース一覧がみれる
  • 既存の環境をコード化できる(Infrastructure as Code)
  • 既存環境と同じ環境を自分の開発環境用に構築できる
テンプレートとは

どのAWSリソースをどのような設定で起動するかコードで記述したもの(JSON形式またはYAML形式)

スタックとは

テンプレートから生成されるAWSリソースの集合のこと
スタックを消すと、紐づくリソースも全て消える

CloudFormer作成手順

※2020年1月時点のGUIのため変わっている可能性があります

  1. CloudFormerのスタックを作成する
    1. CloudFormationサービスの[スタックの作成]ボタンを押下し、[新しいリソースを使用(標準)]を選択する cloudFormer2.png
    2. [サンプルテンプレートの使用]を選択し、テンプレートを[CloudFormer]を選択し、[次へ]ボタンを押下する cloudFormer4.png
    3. [スタックの詳細を指定]でスタックの名前とPasswordを記載して[次へ]ボタンを押下する
      (※Usernameはなくても作成可能) cloudFormer5.png
    4. [スタックオプションの設定]では特に何も指定せず[次へ]ボタンを押下する cloudFormer6.png
    5. [レビュー]で内容を確認し、[AWS CloudFormationによってIAMリソースが作成される場合があることを承認します。]のチェックボックスにチェックを入れて[スタックの作成]ボタンを押下する cloudFormer7.png
    6. CloudFormationサービスのページでスタックのステータスが「CREATE_IN_PROGRESS」から「CREATE_COMPLETE」になるまで待つ(※5分くらい) cloudFormer8.png
    7. スタックのステータスが「CREATE_COMPLETE」になればスタック作成完了 cloudFormer11.png
  2. CloudFormerのスタックを起動する
    1. 作成したスタックを選択し、出力タブに表示されている値のURLをクリックする cloudFormer12.png
    2. CloudFormerツールが起動される
      CloudFormerのスタックを作成した時に指定したユーザ名とパスワードを入力するとアクセスできる cloudFormer14.png
  3. CloudFormerを使用してテンプレートを作成する

    1. 作成したいリーソスのあるリージョンを選択し[Create Template]ボタンを押下する cloudFormer15.png
    2. Introページは特に入力しないまま[Continue]ボタンを押下する cloudFormer16.png
    3. 以降のページではテンプレートに追加したいリソースを選択して[Continue]ボタンを押下していく
      ※セキュリティの関係上キャプチャは割愛
      1. [DNS] では Route 53 レコードを追加できる
      2. [VPC] では Amazon VPC を追加できる
      3. [VPC Network] では Amazon VPC のサブネット、ゲートウェイ、DHCP 設定、VPN 接続を追加できる
      4. [VPC Security] では ネットワーク ACL とルートテーブルを追加できる
      5. [Network] では、Elastic Load Balancing ロードバランサー、Elastic IP アドレス、CloudFront ディストリビューション、Amazon EC2 ネットワークインターフェイスを追加できる
      6. [Managed Services]では Auto Scalingグループ、Elastic Beanstalk アプリケーション、OpsWorks スタックを追加できる
      7. [Managed Config]では Auto Scaling起動設定、Elastic Beanstalk 環境設定、OpsWorks 環境設定を追加できる
      8. [Compute] では Auto Scaling グループと Amazon EC2 インスタンスを追加できる
      9. [Storage] では Amazon EBS ボリューム、Amazon RDS インスタンス、DynamoDB テーブル、Amazon S3 バケットを追加できる
      10. [Storage Config] では Amazon RDS サブネットグループ、ElastiCache パラメータグループ、Amazon RDS パラメータグループを追加できる
      11. [App Services] では ElastiCache クラスター、Amazon SQS キュー、Amazon SimpleDB ドメイン、Amazon SNS トピックを追加できる
      12. [Security] では セキュリティグループを追加できる
        [Security] に進んだ時点で自動的に選択されるため、必要なグループが選択されていることの確認だけでOK
      13. [Operational] ページでは Auto Scaling ポリシーと CloudWatch アラームを追加できる
      14. [Summary (概要)] ページでは次のことができる
        • テンプレートに追加したリソースを確認
        • リソースに割り当てられている自動的に生成された論理名の変更
        • サイトの IP アドレスや URL など、必要な情報を提供する出力内容を指定できる
    4. [AWS CloudFormation Template]に生成されたテンプレートが表示される
      [S3 Bucket]を選択し[Save Template]ボタンを押下してテンプレートをバケットに保存する      cloudFormer32.png
    5. [Launch Stack]を選択すると、指定したAmazonS3バケットにテンプレートが保存された後すぐにスタックが起動する
      [Create Template]を選択した場合は指定したAmazonS3バケットにテンプレートが保存されるだけ cloudFormer17.png

ここまででテンプレート作成完了!!

  1. CloudFomerのスタックを削除する
    1. テンプレートの作成は完了したのでCloudFormerのスタックは必要ないので削除する
      CloudFormationで対象スタックを選択し、[削除]ボタンを押下する
      ※無駄に残しておくと不要な課金されるので不要になったらすぐ削除する cloudFormer33.png

まとめ

β版なので本番環境などには適用することは難しいでしょうが、環境調査用としては十分でした

CloudFormerを使用しなければ管理コンソールから紐付けをひとつずつ辿っていかなければならなかったので
既存環境と同じ環境を作成したいけど設定値がわからない時やテンプレート化しておきたい環境があった場合にとても便利なツールだと思いました

私の場合はJSON形式ファイルのテンプレートの値から環境を調査するのも面倒臭かったので、テンプレートをCloudFormationデザイナーに貼り付けて図として現状を把握し、その図と設定値を元に環境構成図をアップデートしました

今回CloudFormerを使用してリソースを一覧化できたこっとによって、defaultの使用していないサブネットや既に使用していないサービスがあることもわかったので今後はこの情報を元に環境を綺麗にしていきます

私はJSON形式のままテンプレートを使用しましたが、CloudFormationデザイナーを使用してJSONをYAMLに変換して使用することもできるようです

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSのEC2(仮想サーバ)環境構築

まずどこに環境を置くか

AWSはクラウド上であるため、世界中のどこにでも自分の環境を構築できる。それらの中でどのリージョンのどのアべイラビリティゾーンに設置するのかをまずは検討しなければならない。

リージョン

世界中のあらゆる拠点のこと。例えば日本だと「東京リージョン」がある。

アベイラビリティゾーン

実際にサービスを提供する拠点となるデータセンターのこと。
例えば、東京リージョンならばA地域のデータセンターとC地域のデータセンターがある。

EC2

AWSで提供している仮想サーバがEC2(Elastic Compute Cloud)である。
EC2のファイアウォール機能としてセキュリティグループという機能がある。

VPC領域の確保

EC2はVPC(Virtual Private Cloud)と呼ばれる領域のさらに細分化したサブネットと呼ばれる領域内に設置される。
先ほどのEC2に対するセキュリティとしてセキュリティグループの設定があったが、サブネットに対しても同様に**ネットワークACLと呼ばれる設定によってパケットフィルタリングを行う。

インターネットゲートウェイの接続

先ほどのVPCをインターネットに接続するためには、VPC領域にインターネットゲートウェイを接続する必要がある。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS上にMastodon(v3.1.1)を構築した時にハマったこと(非docker)

はじめに

Mastodon(v3.1.1)をAWS上に構築しました。
(非dockerです)
その際にハマったこと、公式手順に追加した手順を記載します。

サーバー構成

  • AWS

今回は社内用の簡易的なものなので、ドメインとELB以外は無料枠内で収まる構成にしました。

Untitled Diagram (2).png

EC2: Ubuntu 18.04

手順とポイント

基本的な手順自体はMastodon公式に則っています。
Installing from source - Mastodon documentation

以下の手順でつまずきました。

precompileが終わらないのでスワップ確保

t2.microインスタンスだとメモリスペックが足りないようで、
スワップ領域を確保してやる必要がありました。
(素のままだとprecompileが終わりませんでした。。)

$ sudo fallocate -l 4G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

AWSは事前に設定しておくとスムーズかも

$ RAILS_ENV=production bundle exec rake mastodon:setup

mastdon:setupで.env.productionを生成してくれます。

.env.productionの設定は後付けでも問題ないのですが、
ツールに任せてしまった方が必要なものだけ作れるので、
事前にAWSの設定は全て完了しておくのが良かったかなと思いました。

そうすると以下のCreate a configuration fileがスムーズかと思います。

This will:
・Create a configuration file
・Run asset precompilation
・Create the database schema

nginxの設定ファイルとポート

手順にはnginxの設定ファイルはMastodonのリポジトリからコピーして使ってね、
という記載があります。

Copy the configuration template for nginx from the Mastodon directory:

cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon

今回は、ELB(443)→nginx(80)→mastodon(3000)という構成なので、
コピーしたファイルを編集する必要がありました。

ちなみに、ファイル名はmastodonとなってますが、
今回はdefaultを上書きする形で対応しました。

コピー元のファイルにはhttp(80)とhttps(443)の設定が記載されていますが、
今回の構成の場合、実際にはhttp(80)のみで動かす必要がありました。
(ドメインにアクセスしてもHTTP 403エラーとなってハマりました...)

編集後のファイルです。

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
  listen 80;
  listen [::]:80;
  server_name yourdomain.com;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_cache CACHE;
    proxy_cache_valid 200 7d;
    proxy_cache_valid 410 24h;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cached $upstream_cache_status;
    add_header Strict-Transport-Security "max-age=31536000";

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://127.0.0.1:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

ちなみにSSL証明書の設定は不要でした。

Acquiring a SSL certificate

おわりに

これで無事にMastodonが構築できました。

参考にさせていただいた記事

AWSでMastodonインスタンスを作るまで。自分まとめ - Qiita

マストドンAWS構築チュートリアル完全版|初心者から大規模運用まで

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウサギでもわかるAmazon FSx for Windows概要と構築

さて、今回はAWSのマネージドファイルサーバーサービス、FSxを触ってみたいと思います。

Amazon FSxとは

簡単に言うとWindowsServerのストレージを利用できるマネージドサービスです。
初めて聞いたときは「ついにWindows Storage Serverもクラウド化したのか~」などと頓珍漢なことを思った記憶があります。
当然ですがOSが丸ごと提供されるEC2とは異なるため、RemoteDesktopによる管理とはまた違う方法で管理する形になります。細かい設定方法は別のサーバー上でPowerShellから実行する必要もあるそうです。

料金は

ファイルサーバーというと前職でもWindowsServerを「これでもか!」というほど構築してきたので、当然EFSとかFSxも発表されたときにそれなりに情報調査をし、提案することができるかを比較してきました。しかし、いまいち料金的に安くないし、管理方法も従来と変わることがデメリットにもなるということで、結論としては提案するところまでいきませんでした。

料金体系は以下の通りです。

  • 容量課金 (バックアップも)
  • 帯域課金

image.png

単純に1TBの容量をバックアップ込みで8MBps程度のスループットで利用する場合は以下の料金となります。

image.png

$202≒2万円超ですか、割とまともな金額ですね。
これが、もう少し帯域が欲しくなると金額が上がります。例えばNuroの回線を使ってRTX1210当たりのルーターでVPN Connectionで接続してもかなりのスループットが出るので、例えば36MBpsの帯域を確保すると以下のようになります。

  • 36MBps × $2.53 = $91

$73値上がりしたので$275です。

EC2で構築する料金は

これを普通のEBS+Snapshotで計算とすると以下のようになります。

  • m5.large 720時間 × $0.216 = $156
  • EBS    1,024GB × $0.12 = $123
  • Snapshot 1,024GB × $0.05 = $51

$330になりました。
なるほど。あまり変わらないどころか、FSxはむしろ安いですね。

ここでちょっと、中小企業向けに料金を最適化してしまいます。

  • t3.large 720時間 × $0.1088 = $78
  • EBS(st1) 1,024GB × $0.054 = $55
  • S3    1,024GB × $0.025 = $25

$158になりました。
EC2はTシリーズ、EBSはスループット最適化ディスク、バックアップにはS3を用いてCLIでファイルコピーとしました。中小企業向けの提案はここまで低価格にしてようやく導入に向けて話を聞いてもらえる、という価格帯でした。

ちなみに風の噂ですが、HDD(st1)を採用するタイプも発売を予定しているそうで、そっちはかなりの低料金になるようです。そっちが本命かもしれませんね。

事前に調べたWindowsServerと異なる点

一般的な利用方法としてはこれまでWindowsServerで利用していたものを移行することが多いと思うので、WindowsServerで出来ていることが出来ないとツラミが深くなってしまいます。そこで、Webを検索して現時点でWindowsServerでできるけど、FSxではできない(っぽい?)情報を集めてみました。

クォータ

各フォルダの最大容量を指定できるクォータ機能が、FSxにもあるのですが、どうも扱いが異なるようです。
WindowsServerでは、フォルダにクォータを設定し、そのフォルダの容量を監視して80%を超えたらメールを発報したり、100%に達すると書き込みが出いなくなるといったルールを設定できる機能です。
しかし、以下のページの説明を見てみると、ユーザごとのファイル容量という基準になっているように見受けられます。ユーザー毎のファイル容量をどのように集計するのか、そのような需要があるのかよくわかりませんし、挙動もよくわからないので、後ほど実際に試してみたいと思います。
https://aws.amazon.com/jp/about-aws/whats-new/2019/11/amazon-fsx-windows-file-server-enables-monitoring-user-level-storage-consumption/
「ストレージ管理者がファイルシステムでのユーザーレベルのストレージ消費を監視および制御」

容量の変更ができない

以下のFSxのFAQページでも容量の変更について一切記載がありません。別のBlog記事ではre:Inventの質疑応答で「現時点ではスナップショットを取得して新しいストレージを作りしかない」と回答があったそうです。ダウンタイムが発生してしまいますね。
https://aws.amazon.com/jp/fsx/windows/faqs/
EBSでも増加はノンストップで可能になっているので、ぜひ早期に実現してほしい機能です。
風の噂では近々リリースされるかもしれません。

アクセスログは取得不可

OSにRemoteDesktopなどでログインできないということは、ファイルシステムの読み書きログの取得もできないと考えたほうがよさそうです。ADサーバで認証ログだけは取得できると思いますが、それだけでは最近のセキュリティ監査がOKとは言えない可能性があります。
そして当然ですが、サーバー側の資産管理ソフトでのログ取得もできません。まぁこれはクライアント側で取得すればいいだけの話です。

ウィルスチェック機能などはなし

単なるストレージサービスなので、ウィルスチェック機能なども提供していません。
Office365のSharepointやOnedriveはウィルスチェック機能を持っているようなので、よりシンプルなIaaSらしいサービスといえます。
これも風の噂ではサービス追加を予定しているとか。

ホスト名の設定不可

作成時にホスト名を入力することができないため、ランダムな文字列のホスト名となってしまいます。
ADでエイリアスを作成することによって回避できる気がしますが、意味不明なホスト名がついているのも気持ち悪いので、こちらもアップデートを待つしかないようです。
もしくは名前空間の利用ですね。DFS-Rとの相性はいいですが、管理面でそこまでやりたくないという場合には面倒になるかもしれません。

移行時のDFS-RはDelfManaged AD & Single AZのみ対応

ファイルの移行は基本的にrobocopyコマンドによる移行が案内されていますが、特定の条件を満たせばWindowsのレプリケーション機能DSF-Rを用いて移行することができるようです。
さらにNameSpaceを用いれは、ノンストップでの移行もできるかもしれません。

FSx for Windowsを使ってみる

さて、発表当初からいろいろとアップデートがあったので、今は東京リージョンでも使えますし、ADもAWSのサービスではなくユーザーの運用しているADサーバがあれば問題なく利用できるようです。

ADサーバの条件は

しかしどのようなADでもよいわけではなく、FSxを利用できる条件がAWSから示されています。
https://docs.aws.amazon.com/fsx/latest/WindowsGuide/self-managed-AD.html

  • 自己管理ディレクトリの完全修飾ドメイン名

注意
ドメイン名は、単一ラベルドメイン(SLD)形式であってはなりません。現在、Amazon FSxはSLDドメインをサポートしていません。

  • ドメインのDNSサーバーのIPアドレス

  • ファイルシステムをADドメインに参加させるためにAmazon FSxが使用する、ADドメインのサービスアカウントのユーザー名とパスワード

  • (オプション)ファイルシステムを参加させるドメイン内の組織単位(OU)

  • (オプション)ファイルシステムで管理アクションを実行する権限を委任するドメイングループ。たとえば、このドメイングループは、Windowsファイル共有の管理、ファイルシステムのルートフォルダーのACLの管理、ファイルとフォルダーの所有権の取得などを行います。このグループを指定しない場合、Amazon FSxはデフォルトでADドメインのDomain Adminsグループにこの権限を委任します。

機械翻訳ですが要件は伝わりますね。
要するにADサーバのIPアドレスと、管理者アカウントがあればOKということです。
専用管理者アカウントを構成することを推奨していて、次のページにその詳細が記載されています。
https://docs.aws.amazon.com/fsx/latest/WindowsGuide/self-managed-AD-best-practices.html

Active Directoryドメインのサービスアカウントを作成するには

  1. Active Directoryドメインのドメイン管理者としてログインしていることを確認してください。
  2. Active Directoryユーザーとコンピューター MMCスナップインを開きます。
  3. 作業ウィンドウで、ドメインノードを展開します。
  4. 変更するOUのコンテキスト(右クリック)メニューを見つけて開き、[ 制御の委任]を選択します。
  5. [ 制御の委任ウィザード]ページで、[ 次へ]を選択します。
  6. [ 追加]を選択して、選択したユーザーとグループに特定のユーザーまたは特定のグループを追加し、[ 次へ]を選択します 。
  7. 上の委任にタスクページ、選択したデリゲートにカスタムタスクを作成し、次に選択し、次へを。
  8. 選択したフォルダ内の次のオブジェクトのみを、その後、選択した コンピュータのオブジェクトを。
  9. 選択して、このフォルダ内の選択したオブジェクトを作成し、このフォルダに選択したオブジェクトを削除します。次に、次を選択します。
  10. [ アクセス許可]で、次を選択します。
  • パスワードを再設定する

  • アカウント制限の 読み取りと書き込み

  • DNSホスト名への検証された書き込み

  • サービスプリンシパル名への検証された書き込み

  1. 選択して[次へ]をして、選択し、完了を。
  2. Active DirectoryユーザーとコンピューターMMCスナップインを閉じます。

FSxの検証準備

ADを立てます

割愛します

ADでFSx管理ユーザーの設定

ADサーバーで「ADユーザーとコンピュータ」を開き、FSxの管理者とするユーザーを作成します。
通常通りドメインユーザーを作成する手順で作成し、特別にグループなどに所属させたりはしていません。
次に右クリックで「制御の委任」を選択します。
image.png
FSxさんを選択します。
image.png
「委任するカスタムタスクを作成する」を選択。
image.png
「フォルダー内の次のオブジェクトのみ」を選択し、「コンピューターオブジェクト」にチェック、さらに欄外の「選択された・・・作成する」と「選択された・・・削除する」両方にチェックを入れます。
image.png
「全般」にチェックが入っているので、その下から「パスワードの変更」「アカウントの制限の読み取りと書き込み」「DNSホスト名への検証された書き込み」「サービスプリンシパル名への検証された書き込み」の4つにチェックを入れます。
image.png

FSxを作成します

サービスのリストではここにあります。
image.png
ここから作成します。
image.png
「FSx for Windows」を選択し、NEXTを押します。
image.png
必要なパラメーターを設定します。
ここで先ほど作成したADの管理を委任したユーザー情報も入力します。
image.png
確認画面でSUBMITすると完了です。
image.png

作成が始まりました。
image.png
今回は最小容量の32GBをデプロイしましたが、完了まで13分間かかりました。
image.png
容量の違いでデプロイ時間に差異があるのかなども、検証してみました。
比較として1TBをデプロイしてみました。
image.png
おっ!14分間でデプロイ完了しました。早いですね。
容量によってデプロイ時間の変化はそれほど大きくないようです。
image.png

使ってみよう

EC2インスタンスへリモート接続し、接続できるか見てみます。
その前にWebコンソールでホスト名(DNS Name)を調べておきましょう。
image.png

早速見てみましょう。

Explolerの表示は

見えました。
「share」という共有ポイントが一つありました。
image.png

当然、普通にアクセスできてNASのように利用することができました。
ちょっとだけプライベートなオンラインストレージが欲しいときにも利用できるかもしれませんね。
(グローバルIPでポート開放するというリスクがありますが)

次回はPowerShellによる各種管理機能を試してみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン、ロードバランサ、サーバの経路がわからなくなった時の経路調査

ドメインがどのような経路でサーバまでたどり着くのかがわからなくなった時の調査の手かがりになる方法をメモしておきます。
もっといい方法があれば更新していきます。

ドメインからホストまでの経路を調べたい

tracerouteコマンドを使えば操作中の端末から目的のサーバまでのネットワーク経路を調べられる。

【 traceroute 】コマンド/【 traceroute6 】コマンド――ネットワークの経路を調べる

ドメインからホストまでの経路を見やすくしたい

RIPE NCCのサービスでip, hostname, ドメインの経路関係を図示してくれる。

RIPE NCC

ドメインがどの業者で使われているか調べたい

WHOIS情報を調べるとXserverなのかAWSなのか等の情報が拾える(かも)

WHOIS情報は以下サイトで調べられる。

WHOIS検索

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CLI v2をDockerでお手軽に試してみる

AWS CLI v2がGAになってますね。
https://aws.amazon.com/jp/blogs/news/aws-cli-v2-is-now-generally-available/

とはいえ手元の環境を変更するのが怖い、という人はDockerでさくっと試してみましょう。

FROM amazonlinux

RUN yum install -y curl unzip jq less

RUN curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN ./aws/install


RUN bash -c 'echo complete -C '/usr/bin/aws_completer' aws  >> $HOME/.bashrc'
ENV PS1="awscliv2> "

WORKDIR /root

あとはDocker Buildして、実行するだけです。

docker build . -t awscliv2
docker run -v $HOME/.aws:/root/.aws -it --rm --name awscliv2 awscliv2

CloudWatch Logでtailができるのは、Lambda使いとしてはとても嬉しい。
https://dev.classmethod.jp/cloud/aws/tail-cloudwatch-logs-group/

と、ここまで書いて、amazonlinuxには最初からv2入っているかも?と思いましたが、気がつかなかったことにします。

謝辞

この記事を参考にさせていただきました。
https://qiita.com/icck/items/40545486a342179a5bd3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GlueのSparkジョブを作る方法(CloudFormation)

以下の様に、CommandのNameに「glueetl」と指定することでSparkタイプで作成できます。

  GlueJob:
    Type: AWS::Glue::Job
    Properties: 
      GlueVersion: '1.0'
      Command: 
        Name: glueetl
        ScriptLocation: !Sub >-
          s3://<ソースコードのパス>
      Name: <ジョブ名>
      Role: <ロールのARN>

この指定が無いと、Python shellとして作成されてしまいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Certifired BigData Specialtyに今さら合格しました。

いまさら受験した経緯

こちらに軽々しく「受けましょうかね」と書いたものの、
2020年4月にAWS Cetified Bigdata Specialtyの試験がなくなるとはつゆ知りませんでした。
情弱ですね。

受験した感想

結論から言うと、非常に受験してよかったと思っています。
Hiveとか、Prestoとか、業務ではあまり触らない仕組みについてハンズオンを行うことで、エンジニアとして多少レベルアップできたかなと思います。普段はあまり触れることがないKinesisやEMRについても理解が深まりました。
あと2か月ほどでこの試験はなくなってしまいますが、勉強して無駄なことは一切ないと思います。

AWS認定資格 取得状況

こんな感じ↓
ソリューションアーキテクト - アソシエイト (2017年10月)
SysOpsアドミニストレーター - アソシエイト (2017年11月)
デベロッパー - アソシエイト (2019年2月)
セキュリティ専門知識 (2019年7月)
ソリューションアーキテクト - プロフェッショナル (2019年9月)
DevOpsエンジニア - プロフェッショナル (2019年12月)
ビッグデータ専門知識 (2020年2月)

試験受験~感想

今回は東銀座の歌舞伎座でした。
ちなみに、DevOpsスペシャリスト以外は歌舞伎座で受験しています。
理由は以下の通りです。

  • 駅直結。雨が降っていても気がめいらずに受けに行ける。
  • 本人確認が楽。チャットとか、ない。
  • ウォーターサーバーがある(!)
  • トイレがきれい(!)

なお、午前中に受験しました。

勉強方法

Udemyの講座とQiitaの以下の記事をなめるようにして読みまわした後、不明な点はハンズオンを行いました。
参照したQiitaの記事は以下の通りです。
本当に勉強になります。

AWS認定Big Data勉強記 - 10: 合格しました
こちら、本当に勉強になります。各サービスについてわかりやすくまとまっていました。

[AWS]kinesisまとめ
Kinesisはハンズオンしても正直わかりにくかった部分がありますが、
この記事をなめるようにして読みました。本当にわかりやすいです。

Udemyはこちらを勉強しました。
https://www.udemy.com/course/aws-big-data/
わかりやすいと思います。

また、ブラックベルトで3回以上読み込んだサービスは以下でした。本番でもたくさん出てきました。
・Kinesis
・Redshift
・QuickSight
・Aurora
・DynamoDB

本番で悩んだところ

  • SQS vs Kinesis

→ずーっと悩みました。最後は勘。
SQSについてもっと勉強しておけばよかったです。

  • DMSによる移行 vs DMS + SCTによる移行

→こんなんで悩むとはまだまだだと思われるかもしれませんが…
悩みましたorz

以上です。
次は高度なネットワーキングでも受験してみます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モチベーションが低いダイナモDB入門

はじめに

色々と訳あり、ダイナモDBを練習してみます。

事前知識

MySQLなどの知識は少しあります。
FirebaseでNoSQLには触ったことあります。

手順

AWSにアカウント作成済みとします。
pip3もインストール済みとします。

(1)AWS CLIインストール

pip3 にてAWS CLIをインストールします。

pip3 install awscli --upgrade --user

(2)テーブルを作成する

AWSにログインし、DynamoDBを選択。
デカデカと出ているテーブル作成をクリックします。
image.png

テーブル名を[moti]、プレマリキーを[downs]にします。
ついでにソートキーは[num]にします。
image.png
よくわからないので「デフォルト」のまま作成します。

image.png

できたようです。

(3)awsでアカウントを作ります

IAMというのを作らないとAWS CLIで使えないので作ります。
IAMを選択し、ユーザを追加します

image.png

プログラムからのアクセスにチェックを入れます。

image.png

ダイナモDB系の権限を許容します
image.png

タグを作らず進めます

アクセスキーとシークレットキーをコピーしておきます
image.png

下記のようにawscliにログインします。

$ aws configure --profile hashito
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxx
Default region name [None]: hashito
Default output format [None]: 

※一番下にもありますが…これ間違えています…
 Region→ap-northeast-1、output formatはtextなどがいいです。

(4)awscliで操作します

こんな感じのコマンドでデータを追加するようです。

$aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "2"}, "created_at": { "S": "1544741492" }, "message": { "S": "aaaaaaaaaaaaaa" } }'

よく読むとわかりますが基本的にJSON形式の入れ子になっています。
最も上の層が属性のキー名で次の層が属性(SはString、NはNumber的な感じ)になっています。

{"key name":{"type char":"var"}}みたいな感じですね

追加されています
image.png

table一覧

$ aws dynamodb list-tables
TABLENAMES  moti

データ取得

$aws dynamodb get-item --table-name moti --key '{ "downs": { "S": "1" }, "num": { "S": "2"}}'
CREATED_AT  1544741492
DOWNS   1
MESSAGE aaaaaaaaaaaaaa
NUM 2

(5)色々と試す。

プレマリキーが同一の場合はどうなるか

$aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "2"}, "created_at": { "S": "1544741492" }, "message": { "S": "aaaaaaaaaaaaaa" } }'
$aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "3"} }'

上書きされず、別データとして扱われます。

image.png

プレマリキーとソートが同一の場合はどうなるか

$aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "2"}, "created_at": { "S": "1544741492" }, "message": { "S": "aaaaaaaaaaaaaa" } }'
$aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "2"} }'

上書きされます。
image.png

いくつかまとめて持ってくるには?

雑にデータを追加します

$ aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "3"} }'
$ aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "1" }, "num": { "S": "2"} }'
$ aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "2" }, "num": { "S": "2"} }'
$ aws dynamodb put-item --table-name moti --item '{ "downs": { "S": "3" }, "num": { "S": "2"} }'

$ aws dynamodb query --table-name moti --key-condition-expression 'downs =:_downs' --expression-attribute-values '{ ":_downs": { "S": "1" }}'
None    2   2
DOWNS   1
NUM 2
DOWNS   1
NUM 3

--expression-attribute-valuesというので変数を定義して、--key-condition-expressionでwhereみたいにするようですね。

感想

NoSqlを初めて触ってみましたが楽しいですね。
ただ、SQL的なのがややこしい…
そもそも、あまりこれで条件区切って持ってこないのかもしれないです。

(詰まった)

(詰まった)'aws'コマンドがない

インストールしたはずのawsコマンドが存在しませんでした。

$ aws help
-bash: aws: command not found

どうやらPython3-pipのインストールフォルダにパスが通っていないことが原因でした。
まず、Python3-pipでインストールしたフォルダを確認します。

$ pip3 show awscli
Name: awscli
Version: 1.18.0
Summary: Universal Command Line Environment for AWS.
Home-page: http://aws.amazon.com/cli/
Author: Amazon Web Services
Author-email: UNKNOWN
License: Apache License 2.0
Location: /Users/{user}/Library/Python/3.8/lib/python/site-packages
Requires: botocore, PyYAML, docutils, s3transfer, rsa, colorama
Required-by: 

ここにはないようです…
こちらの記事を参考に…

AWS CLI の パス を通す
https://qiita.com/plum_shiga/items/5214510f9786898c987f

私のはここにありました。

$export PATH=/Users/{user}/Library/Python/3.7/bin:$PATH
$aws -help
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
aws: error: the following arguments are required: command

(詰まった)Could not connect to the endpoint URL: "https://dynamodb.hashito.amazonaws.com/"って出る。

雑に設定しすぎてaws configureで設定したリージョンが異なっていたようです。

AWS Access Key ID [****************]: 
AWS Secret Access Key [****************]: 
Default region name [hashito]: ap-northeast-1
Default output format [hashito]: text

設定し直しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む