読者です 読者をやめる 読者になる 読者になる

CloudFront にCloudFront を入れるときの注意点

CloudFront は、Request header に含まれる Hostの値が、CloudFrontに設定されたCNAMEと一致していないとレスポンスを返さない。
CloudFrontに設定されたCNAMEは、すべてのCloudFrontで2つ同じCNAMEを設定できない。

Behavior の Forward Headers は、Host を含めた場合は、ブラウザがリクエストしたHostがそのままOriginまで到達するが、
含めない場合は、Originに設定したHostのまま、Originへリクエストする。
実はここがあまり書かれていないのでわかりにくい。

この条件を満たすため、
CloufFrontのOriginにCloudFront を含めるような場合は、Behavior の Forward Headers は、All が設定できず、Whitelist を使わなければならない。
たとえば All の代わりに、User-Agent、Authorization などを設定することになるはずです。

mysql5.7 の 注意点 (< 5.7.11)

RDS 上で mysql 5.7 を使うときに覚えておかなければならないことは一つで、

default_password_lifetime
この値が、5.7.10 以下の時、360 になっています。

SELECT @@default_password_lifetime;

つまり360日で期限切れになりログインできなくなります。RDS 作成時のマスターパスワードであっても同じです。

Your password has expired. To log in you must change it using a client that supports expired passwords.

万が一、期限が切れてしまった場合は、
RDSのマスターパスワードをリセットし、mysqlにログイン。

ALTER USER user_name PASSWORD EXPIRE NEVER;

これで password_lifetime = 0 となり、ログインできます。

5.7.11以降のバージョンは、default_password_lifetime = 0 になっているので、問題はありません。

Route53 で Domain name status code がブランクな場合

jp や、io のドメインで確認しましたが、Domain name status code がブランクのままになるドメインがあります。
whois コマンドで、

[状態] Active

となっていれば、気にせず安心して良いと、AWSに回答いただきました。

CloudFront lambda@Edge で、Basic認証を設定する

S3+CloudFrontなど、サーバーレスでBasic認証をかける方法を何年も検討していたのですが、CloudFrontにLambdaを割り当てられるようになり、試したところ実装することがで来ました。

まだPreview版ですが、フォームから申請することで、数日で許可が得られます。

イベントの種類

まず仕組みとしては、以下4つのイベントに、機能が絞られたLambda@Edgeを割り当てることがで来ます。

Viewer リクエスト

リクエスト時に呼ばれるイベント

Viewer レスポンス

レスポンス時に呼ばれるイベント

Origin リクエスト

オリジナルデータにリクエストが到達した時に呼ばれるイベント

Origin レスポンス

オリジナルデータからのレスポンス時に呼ばれるイベント

リクエストとレスポンスの違い

レスポンスの場合
  • レスポンスヘッダーの参照
  • レスポンスヘッダーの書き換え
  • レスポンスを返す

こちらはあまり使うことを検討していないので、もしかしたら他の機能もあるかもしれません。

リクエストの場合
  • リクエストヘッダーの参照
  • Origin へのリクエストヘッダーの書き換え
  • Origin の切り替え
  • Origin にアクセスせず、その場でレスポンスを返す

status が callback に渡した連想配列に含まれているかどうかで、Originへアクセスするかどうかの挙動が変わるようです。



今回は Viewer リクエストを使います。
Basic 認証の仕様に合わせてヘッダーを返すことで、Basic認証を実装することがで来ました。

exports.handler = (event, context, callback) => {
    
    const Allows = {
        "users": "users",
    };
    
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const authorization = headers.authorization || headers.Authorization;

    if (authorization) {
        
        const enc = authorization[0].split(" ")[1];
        const userPassword = new Buffer(enc, 'base64').toString();
        
        for (var key in Allows) {
            var val = Allows[key];
            if (`${key}:${val}` === userPassword) {
                callback(null, request);
                return
            }
        }
    }

    const response = {
        status: '401',
        statusDescription: 'Authorization Required',
        httpVersion: request.httpVersion,
        headers: {
          "WWW-Authenticate": ['Basic realm="Enter username and password."'],
          "Content-Type": ["text/plain; charset=utf-8"],
        },
        body: "401 Authorization Required",
    };

    callback(null, response);
};

event のサンプル

※ event に入ってくるデータは、以下のように小文字でくることもあれば、大文字始まりで受け取る場合もあるので、どちらにも対応しなければならないようです。

{
  "host":["dummy.dayo"],
  "authorization":["Basic dXNlcnM6dXNlcnM="],
  "cache-control":["no-cache"],
  "user-agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"],
  "accept":["*/*"],
  "accept-encoding":["gzip, deflate, sdch, br"],
  "accept-language":["ja,en;q=0.8,en-US;q=0.6"]
}


ログがでないと言うことと、Lambda@Edgeを割り当てたときだけでなく、コードを書き換えたあとも、数分待ち時間があることで、確認が大変でした。

ElasticBeanstalk+CloudFrontのときにも、このBasic認証の設定が活躍するのでうれしいです。

SES でメール送信時にリジェクトされる

Net::SMTPFatalError: 554 Message rejected: Email address is not verified. The following identities failed the check in region US-EAST-1

ActionMailer で送信した例だが、このようにメール送信がデフォルト状態だと出来ない。

Your Amazon SES account has "sandbox" access in region US East (N. Virginia). With sandbox access you can only send email to the Amazon SES mailbox simulator and to email addresses or domains that you have verified. To be moved out of the sandbox, please request a sending limit increase. Learn more.
Can't find your existing account settings? Your account may be set up in a different AWS region. Try switching regions in the upper right corner of the console.

SESはデフォルトの状態だと、sandboxモードになっており、テスト用の特定アカウントか、登録したメールアドレス、送信元と同じメールアドレスにのみ、送信できるようになっている。

サポートへ問い合わせることで、送信制限と緩和と同時に、sandboxモードが解除され、どのメールへも送れるようになる。

Lambda@Edge を利用し、Cloudfrontに設定ようとしたとき、エラーが表示される

There was an error creating the trigger: Completing this action would cause the maximum number of distributions with Lambda function associations per owner to be exceeded (maximum allowed is 0).

Lambda@Edge は現在プレビュー版となっており、利用する場合は事前に申請の必要があります。
申請は下記のページより

https://pages.awscloud.com/lambda-at-edge-preview.html

AWS SSM で、EC2に任意のコマンドを実行する

ssm は、ssh で接続することなく、 必要な role があれば、特定の EC2 上へコマンドを送ることが出来ます。
Amazon Linux に対しても、実行できます。

EC2の自動セットアップを行いながらも、ssh キーの登録をしたくないようなときに役立ちます。

実行するリモートサーバー側に ssm-agent をインストールする。

cd /tmp
curl https://amazon-ssm-ap-northeast-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm -o amazon-ssm-agent.rpm
yum install -y amazon-ssm-agent.rpm

実行するリモートサーバー側に role を設定する。

AmazonEC2RoleForSSM

コマンドを送る元に、role を設定する。

AmazonSSMFullAccess

実行方法

こんな感じ

def command(instance, commands):
  ssm = boto3.client('ssm')
  r = ssm.send_command(
    InstanceIds = [instance.instance_id],
    DocumentName = "AWS-RunShellScript",
    Parameters = {
      "commands": commands
    }
  )
  command_id = r['Command']['CommandId']

  while True:
    time.sleep(0.2)
    res = ssm.list_command_invocations(CommandId=command_id)

    invocations = res['CommandInvocations']
    if len(invocations) <= 0: continue

    status = invocations[0]['Status']
    if status == 'Success': return True
    if status == 'Failed': return False