AWS Elasticsearch ServiceがVPC対応したので、lambda→ESをVPCで括ってみた

VPC Support for Amazon Elasticsearch Service Domains – Amazon Elasticsearch Service http://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/es-vpc.html

FireShot_Capture_065_-_Amazon_Elasticsearch__-_https___ap-northeast-1_console_aws_amazon_com_es_home.png

やること

  1. VPCの設定をする
  2. Elasticsearch Serviceを新規作成してVPC設定する
    (既存のドメインをVPC対応にする・VPCを切り替える方法はまだわからない)
  3. lambdaにVPCを設定する
  4. テスト

VPC設定

  1. プライペートとパブリックの2パターンサブネットを作成する
  2. パブリックサブネット内でNATゲートウェイを作成する
  3. NAT,インターネットゲートウェイをルートテーブルに割り当てる
  4. ESに設定するセキュリティグループを設定する

ES設定

  1. 新しいドメインの作成
  2. ネットワーク設定でVPC, サブネット(プライペート), セキュリティグループを設定する
  3. アクセスポリシーをVPC用に設定
    FireShot_Capture_066_-_Amazon_Elasticsearch__-_https___ap-northeast-1_console_aws_amazon_com_es_home.png

lambda設定

  1. ロールにVPCアクセス権限をアタッチする
  2. lambdaをプライペートサブネットに配置する

API Gateway設定

(疎通確認だけなら、lambda関数に繋がればよいかと)

疎通確認

ESはVPC内なので、外のネットワークからはアクセスできない
そのため、Kibana見れない状態…
VPC内にWindows立ち上げるとかEC2入れてcurlで叩いてみるとか

lambda内のログに出力するなりで確認

はまりどころ

lambdaにパプリックネットワークのサブネットを付けてしまっていた為、外に接続できなかった
原因をAPI Gatewayかと思い、メソッドを設定したものの解決せず時間がかかってしまった

もしかして

ESへのアクセスを制限したいだけであれば、lambdaをVPC内に設置してNAT Gateway経由でESにアクセスすればIP許可だけで十分だったのでは…?

続きを読む

API Gateway + AWS Lambda で%26が渡せなかった話

謎のInternal server error

https://example.com/v1/?n=[name]
みたいな感じに、API Gatewayでnパラメータを受けて、lambdaで処理する仕組みを作ったら、nパラメータに”a&z” (URL Encode: “a%26z”)みたいな”&”を含むデータを渡すと謎のInternal server error出てハマった。

{
message: "Internal server error"
}

ClowdWatchLogにはsignatureがどうのこうののエラーが。

  • ClowdWatchLog
Execution failed due to configuration error: 
The request signature we calculated does not match the signature you provided. 
Check your AWS Secret Access Key and signing method. 
Consult the service documentation for details.

原因

サポートに問い合わせたところ、以下の回答が。


The error is because the query string “a%26z” is decoded as “a&z” by CloudFront viz. the URL Encoded form. So, the API Gateway gets the query string as “a&z”. Now, when the invocation URL to lambda is being created it appends the Query String given in Integration Request of that resource. Now, the URL is
similar to “https://lambda..amazonaws.com/2015-03-31/functions//invocations?name=a%26z” but while calculating the signature [1] it treats as invalid because “/invocations?name=a%26z” is not a valid lambda invocation URL. The correct Lambda invocation URL is explained in this doc [2].

Moving forward, if you want to use a%26z to your backend then the correct way is to pass it like “a%2526z” In query string and have a body mapping template.

[1]. http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
[2]. http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#w2ab1c67c12c49c11


知識のなさと英語力のなさでいまいち原因を理解できていないが(英語とAWS得意な誰か解説してください:pray:)、2重URLエンコードして %2526 にすればエラーにならないということは理解できた。Lambda内で一度URL Decodeしないといけないが。

解決

2重URLエンコードするのと同じようなものだが、結局、データをすべてBASE64でエンコードしてLambdaでDecodeすることにした。

疑問

既に多くの人がここではまっている気がするんだけど、ググっても全然情報ないのはなぜ?

続きを読む

Amazon SESで送信元と宛先の制限をかけてみたメモ

調べたり試したりしてみたので、メモ的な。

制限するには

 もともとSESにはサンドボックスモードと言って制限がかかった状態でサービスが利用可能になる。
  参照:http://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/limits.html
 その制限をわざわざ解除申請を出して自由に利用できるようにする。

 けど、自由すぎても困って、という話。

 SESを利用する際には、SES用のIAMユーザーを払い出して利用します。
 そのIAMユーザーのポリシーを利用して幾つかの制限ができるようです。
  参照:http://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/control-user-access.html

設定

送信元制限

 特に問題なくできてしまったので、その時のポリシー例を。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1507889923000",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Condition": {
                "StringLike": {
                    "ses:FromAddress": [
                        "*@example.com"
                    ]
                }
            },
            "Resource": [
                "*"
            ]
        }
    ]
}

 これで「なんちゃら@example.com」が送信元の場合に限り、IAMはSESの認証をできるようにしてくれます。
 例えば「なんちゃら@jp.xxxx.com」とかが送信元になった場合に、554 Access deniedしちゃいます。
 これが送信元制限の簡単なやり方。

送信先(宛先)制限

 送信元制限のノリで作業したらはまったので、このメモを残すことになった。
 先に参照として出したドキュメントに、「ses:Recipients」使ったらできるで、と書いてあったので、間に受けて設定してたんですが
 それが間違いでした。
 とりあえず、うまくいった例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "ses:Recipients": [
                        "*@example.com"
                    ]
                }
            }
        }
    ]
}

 この設定で、「*@example.com」以外にメールが送信できなくなり、先ほどの送信元制限と同様に554エラーが発行されます。
 何にはまったかというと、"ForAnyValue:StringLike" この項目。

 送信元制限の際は、「”StringLike”」でいけるんですが、サポートにお聞きしたところ、ses:Recipientsは複数の項目にまたがり値があるので
 "ForAllValues:StringLike"
 を使ってな、と。
 ドキュメントにも例でのせてるで、ってなことなので、やったんですがダメでした。
 曰く

注記
キーに複数の値が含まれる場合、設定演算子(ForAllValues:StringLike および ForAnyValue:StringLike)を使用して StringLike を修飾できます。

 なので、ダメ元で ForAnyValue:StringLike を使ったらいけたという。
 実はAllとAnyの利用の違いについては調べ切れてないんですが、やりたいことができたということなので、これにて。

両方制限したい欲張りなあなたへ

 以下の設定で実現できてます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringLike": {
                    "ses:Recipients": [
                        "宛先アドレス"
                    ],
                    "ses:FromAddress": [
                        "送信元アドレス"
                    ]
                }
            }
        }
    ]
}

 アドレスを指定する場合はLikeじゃなくてEqualesでもいいかもしれませんね。

続きを読む

Web ページから、AWS Lambda の関数に引数付きでアクセスする

次の AWS Lambda 関数に、API Geteway 経由でアクセスする方法です。
これは、3つの整数、aa,bb,cc を与えて合計 sum を計算するプログラムです。

sum_up.py
# -*- coding: utf-8 -*-
#
#   sum_up.py
#
#                   Oct/20/2017
#
# --------------------------------------------------------------------
import sys
import json

# --------------------------------------------------------------------
def sum_up_handler(event, context):
    sys.stderr.write("*** sum_up_handler *** start ***\n")
#
    aa = 0
    bb = 0
    cc = 0
#
    sys.stderr.write("Received event: " + json.dumps(event, indent=2) + "\n")
    if ('aa' in event):
        sys.stderr.write("aa = " + event['aa'] + "\n")
        aa = int(event['aa'])
#
    if ('bb' in event):
        sys.stderr.write("bb = " + event['bb'] + "\n")
        bb = int(event['bb'])
#
    if ('cc' in event):
        sys.stderr.write("cc = " + event['cc'] + "\n")
        cc = int(event['cc'])
#
    version = "Oct/19/2017 AM 16:42"
#
    sum = aa + bb + cc
#
    sys.stderr.write("version = " + version + "\n")
    sys.stderr.write("sum = " + str(sum) + "\n")
    sys.stderr.write("*** sum_up_handler *** end ***\n")
#
    rvalue = {}
    rvalue['aa'] = aa
    rvalue['bb'] = bb
    rvalue['cc'] = cc
    rvalue['sum'] = sum
    rvalue['version'] = version
    rvalue['language'] = 'Python'
#
    return rvalue
#
# --------------------------------------------------------------------

1) これを、AWS Lambda の関数にする方法は、

python の関数を AWS Lambda で使用する

2) API Gateway の設定は、

API Gateway から Lamda にアクセスする

3) API Gateway で cors を有効にします。

cors_oct1901.png

4) Web ページとスクリプトです。jQuery を使います。

cors.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />
<script src="jquery-3.2.1.min.js"></script>
<script src="cors.js"></script>
<title>AWS API Gateway</title>
</head>
<body>

<div id="answer">answer</div>


<hr />
<div id="outarea_aa">outarea_aa</div>
<div id="outarea_bb">outarea_bb</div>
<div id="outarea_cc">outarea_cc</div>
<div id="outarea_dd">outarea_dd</div>
<div id="outarea_ee">outarea_ee</div>
<div id="outarea_ff">outarea_ff</div>
<div id="outarea_gg">outarea_gg</div>
<div id="outarea_hh">outarea_hh</div>


<hr />
Version: Oct/19/2017 PM 15:19<br />

</body>
</html>

次のプログラムの rest_api_id は、変更して下さい。

cors.js
// -----------------------------------------------------------------------
//
//  cors.js
//
//                      Oct/20/2017
//
// -----------------------------------------------------------------------
jQuery (function ()
{
    jQuery("#outarea_aa").text ("*** cors.js *** start *** PM 18:28")

    getPost()

    jQuery("#outarea_hh").text ("*** cors.js *** end ***")
})

// -----------------------------------------------------------------------
function getPost()
{
    const rest_api_id="bbc2fbufm6"

    const url="https://" + rest_api_id + ".execute-api.ap-northeast-1.amazonaws.com/test"

    const params = {'aa': '121','bb': '304','cc': '256'}

    const args = JSON.stringify(params)

    jQuery.post(url,args,function(res)
        {
        const sum = res.sum
        var str_out = ""
        str_out += "aa = " + params['aa'] + "<br />"
        str_out += "bb = " + params['bb'] + "<br />"
        str_out += "cc = " + params['cc'] + "<br />"
        str_out += "sum = " + sum + "<br />"
        jQuery("#answer").html (str_out)
        })
}

// -----------------------------------------------------------------------

続きを読む

【注意】Amazon Aurora(MySQL)でZero Downtime Patch実行時に不具合が発生するケースがある【AWS】

はじめに

先日、Amazon Aurora(MySQL)の必須アップデートの案内が来たのでアップデートの検証作業を実施しておりました。
Zero Downtime Patch(以下、ZDP)が実行され、ダウンタイム無しでアップデートが行われるはずが、不具合が発生してしまいました。

ググっても関連する情報が見つからなかったので、取り急ぎ分かっている情報について記録しておきます。

なお、対象のアップデートは下記のものになります。(Cluster Version 1.14)

Database Engine Updates 2017-08-07
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

ポイント(TL;DR)

重要なポイントは下記のとおりです。

Aurora(MySQL)に対し、DB Connection Poolを利用し、且つPrepared Statementを利用していると、Zero Downtime Patch実行時に不具合が発生する可能性が高い。
Aurora(MySQL)のアップデートを行う前に、アプリの構成・実装を調べた方が良い。

Zero Downtime Patch(ZDP)とは何か

Auroraを無停止(正確には5秒程度の遅延)でアップデートできる画期的な仕組みです。
アップデート処理中にも、クライアントからのDB接続は維持されます。

今回、Cluster Version 1.14へのアップデートとなりますが、本アップデートに関するドキュメントにも説明があります。
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

AuroraのCluster Version 1.10で初めて導入されたようです。
ZDPの内容について、下記ドキュメントにも記載があります。

Database Engine Updates 2016-12-14
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20161214.html

こちらの記事も情報がまとまっていて参考になります。

Amazon Aurora のアップグレード
https://qiita.com/tonishy/items/542f7dd10cc43fd299ab

事象の内容

Aurora(MySQL)でClusterのアップデートが必要の旨の案内が来たため、AWS Management Consoleからアップデートを行いました。
AuroraのClusterアップデートは無事に完了しましたが、当該Auroraインスタンスを利用しているJavaアプリが軒並みエラーを吐き出し始めました。
その後Javaアプリを再起動し、復旧しました。

AWS Management Console上、WriterとなっているインスタンスのEventに下記のものが表示されました。
従って、Zero Downtime Patchが行われていたことは間違いありません。

The database was successfully patched with a zero downtime patch.

Javaアプリのログ上には、「Unknown prepared statement handler」といったエラーが多数出力されておりました。

事象の原因

当該JavaアプリはJDBC Connection Poolを利用し、DBとの接続を維持する構成になっています。
また、Prepared Statementを利用する実装になっております。

ZDP実行時、MySQLのクライアント(i.e. Javaアプリ)からの接続は維持されるようですが、Prepared Statementは維持されないことが原因のようでした。
時系列的に整理すると、下記のような状況になっていたようです。

  1. Javaアプリ起動時、Auroraに対して幾つかJDBC接続を確立する
  2. リクエストに応じて、JavaアプリからAuroraにクエリを発行する。この際、Prepareを発行してPrepared Statementを作る。
    Javaアプリは、同一のリクエストに対してはPrepared Statementを再利用する。
  3. AuroraのアップデートをZDPで行う。この際、JavaアプリからのJDBC接続は維持されるが、Prepared Statementは全てクリアされる。
  4. Javaアプリに対し、項番2と同様のリクエストを行う。Javaアプリは、コンパイル済みのPrepared Statementが存在しているという認識のまま、これを再利用しようとし、エラーとなる。

同一のアプリに対し、Prepared Statementを利用するもの/しないものを用意してAuroraのアップデートを実施しました。
結果、前者はエラーが発生し、後者はエラーが発生しませんでした。

なお上記の挙動は、独自に調査・検証したものとなります。
現在、AWSの公式見解・ドキュメント等の情報は見つけられておりません。
# 一応、Database Engine Updates 2016-12-14には以下の記載がありますが、これが該当するかどうかは不明です。
# Note that temporary data like that in the performance schema is reset during the upgrade process.

2017/9/26にAWSサポートへの問い合わせを行っておりますが、2017/10/19現在、回答待ちの状況です。

確認した環境・構成

上記事象は、下記構成のJavaアプリで確認しました。

  • Java 8 (Oracle JDK)
  • spring-boot-1.4.0.RELEASE
  • mybatis-spring-boot-starter 1.1.1
  • mariadb-java-client 1.5.5

併せてMyBatisGeneratorも利用しています。
特別な理由がない限り、JavaアプリからはMyBatisGeneratorで生成されたMapperとClassを利用しています。

デフォルトでは、MyBatisGeneratorではPrepared Statementを利用するMapperを生成するようです。

回避策

回避策は概ね以下の3通りになると思います。

1.Prepared Statementを利用しないよう、アプリを変更する。

ソースコード/ORマッパーの改修・設定変更を行い、Prepared Statementを利用しないようにする方法です。
おそらく、この対応は難しいケースがほとんどだと思います。

2.MySQL接続ドライバの設定で、Prepared Statementを利用しないよう変更する。 

今回のケースでは、MariaDB Connector/Jを利用しており、オプションを指定することでPrepared Statementを利用しないように変更することができました。

useServerPrepStmtsオプションをfalseにすることで実現可能です。
ちなみに、1.6.0以降のバージョンはデフォルトでfalseになっているようですので、これ以降のバージョンを利用してる場合は、本件の影響を受けません。

About MariaDB Connector/J -> Optional URL parameters -> Essential options
https://mariadb.com/kb/en/library/about-mariadb-connector-j/

3.ZDPではない、通常のアップデートを実施する

Auroraインスタンスの再起動を伴う、通常のアップデートを実行することで、本件事象を回避することが可能と考えられます(※未検証)
20~30秒程度のダウンタイムが発生します。

下記に、ZDPにならないケースについて記載がありますので、このどれかを満たせば通常のアップデートになると推測できます(※未検証)

Database Engine Updates 2017-08-07 -> Zero-Downtime Patching
http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Aurora.DatabaseEngineUpdates.20170807.html

  • Long-running queries are in progress
  • Open long-running transactions exist
  • Binary logging is enabled
  • Binary log replication is running
  • Pending parameter changes exist
  • Temporary tables are in use
  • Table locks are in use
  • Open SSL connections exist

まとめ

DBへのConnection Poolを利用し、且つPrepared Statementを利用するようアプリを実装していると、不具合が発生する可能性が高いです。

経験的な感覚ですが、JavaアプリはConnection Poolを利用しているケースが多いかと思います。
Aurora(MySQL)を利用している場合は、チェックをした方が良いかと思います。

検証作業について

古いClusterバージョンのAuroraインスタンスを作ることはできません。AWSサポートにも聞いてみましたが無理なようでした。
従って、運良く過去に構築した古いAuroraインスタンスが存在していれば検証は可能ですが、それ以外のケースでは検証手段がありません。

続きを読む

Angular4 + Amazon Cognitoで認証画面作ってみた

いまさらながらAmazon Cognitoを使って認証画面を作ってみた備忘録。
せっかくなのでAngular4ベースで画面は作成。

amazon-cognito-identity-jsで認証

amazon-cognito-identity-jsを利用してみます。インストールは簡単。

$ npm install amazon-cognito-identity-js --save

ユーザプールを作成しユーザ情報を追加

ユーザプールを作成。設定はとりあえず全てデフォルトを選択しました。で、ユーザを追加。ただしAWSコンソールからユーザ追加をするとメールなどによる承認手続きが必要になってしまうので、手抜きのためにコマンドでユーザ追加と承認処理を実施。

$ aws cognito-idp sign-up --client-id <ClientId> --username <Username> --password <Password> --user-attributes Name=email,Value=<Mail Address>
$ aws cognito-idp admin-confirm-sign-up --user-pool-id <UserPoolId> --username <Username> 

無事ステータスが「CONFIRMED」のユーザが出来上がりました。(CONFIRMEDステータスでないと、APIでログインを試行してもVerifyしろ、というエラーが返ってきてしまう)

スクリーンショット 2017-10-18 19.25.38.png

画面作成の参考ページ

このページを参考に認証画面ぽいモノを作成しました。

http://www.fumiononaka.com/Business/html5/FN1704013.html

すでにバージョンの違いからパッケージの内容が若干変わっているところはありますが、特に問題なく作成することはできました。

それっぽい画面の作成

さくっとAngular4(なのかなこれ?)で作ります。

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';
import { Http } from '@angular/http';
import { CognitoService } from './cognito.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [CognitoService]
})
export class AppComponent {
  userName = '';
  password = '';

  constructor(
    private cognitoService: CognitoService,
    private http: Http
  ) { }

  signIn() {
    this.cognitoService.signIn(this.userName, this.password);
  }
}

app.comoponent.html
<!--The content below is only a placeholder and can be replaced.-->
<div>
  <h1>
    Amazon Cognito Test login
  </h1>
</div>
<div>
  <div id="loginId">
    <div>
      <label>Login ID: </label>
    </div>
    <div>
      <input type="text" [(ngModel)]="userName" />
    </div>
  </div>
  <div id="loginPass">
    <div>
      <label>Password: </label>
    </div>
    <div>
      <input type="password" [(ngModel)]="password" />
    </div>
  </div>

  <div id="signIn">
    <button (click)="signIn()">Sign In</button>
  </div>
</div>

Cognito用サービスは、最低限サインインだけを実装しました。

cognito.service.ts
import { Injectable } from '@angular/core';
import * as AWS from 'aws-sdk';
import { CognitoUserPool, CognitoUserAttribute, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
import { environment } from '../environments/environment';

@Injectable()
export class CognitoService {
    userPool = null;
    constructor() {
        AWS.config.region = environment.region;
        const data = { UserPoolId: environment.userPoolId, ClientId: environment.clientId };
        this.userPool = new CognitoUserPool(data);
    }

    signIn(userName, password) {
        const userData = {
            Username: userName,
            Pool: this.userPool
        };

        const cognitoUser = new CognitoUser(userData);
        const authenticationData = {
            Username: userName,
            Password: password
        };

        const authenticationDetails = new AuthenticationDetails(authenticationData);
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: function (result) {
                alert('Sign in succeded');
            },
            onFailure: function (err) {
                alert(err);
            }
        });

        return;
    }
}

あとはこれらを使うための環境変数を記述すればOKです。

environment.ts
export const environment = {
  production: false,
  region: 'xxxxxxxxx',
  userPoolId: 'xxxxxxxxxxxxxxxxxxxxx',
  clientId: 'xxxxxxxxxxxxxxxxxxx'

};

結果はこちらに。

https://github.com/kojiisd/angular-cognito

アクセスしてみる

登録したユーザでアクセスしてみます。無事アクセスできました。

スクリーンショット 2017-10-18 19.31.46.png

アカウント情報を間違えたらもちろんアクセスできません。

スクリーンショット 2017-10-18 19.31.53.png

まとめ

だいぶ手抜きですが、Angular4ベースでCognitoを利用する画面を作成しました。Routerなどを実装できれば、それなりにCognitoを使ってのSPAが作成できそうなイメージも持てました。Angular4の勉強はもっとしないとなぁ。。。

続きを読む

[Laravel5.4] Proxy環境下でSSL強制

5年ほどROM専だったけど気が向いたので投稿。
最近業務でLaravel使うこと多くなってきた。

前提

  • Laravel 5.4
  • PHP7.1
  • AWS + CloudFront(CDN)
  • Port443空いてる Port80閉じてる

解決したいこと

まーあるあるネタ

  • 普通にLaravel使ってると、リダイレクト時やblade でリンク生成時、
    最終的にUrlGeneratorでURL生成してることがほとんど
  • UrlGeneratorはアクセス元のスキームやホストからフルURLを生成する
  • Proxy環境下ではクライアントからのアクセスはHTTPSでも、エンドポイントから見るとHTTPなこと多し
  • よってUrlGeneratorがクライアント側がHTTPSでもhttpなURLを生成しちゃう

解決方法

ミドルウェアでリクエストを確認して、HTTPSなアクセスだったらスキーマを書き換える

ミドルウェア作成

app/Http/Middleware/SecureAccess.php
<?php
namespace AppHttpMiddleware;

use SymfonyComponentHttpFoundationRequest;

class SecureAccess
{
    public function handle($request, Closure $next, $guard = null)
    {
        $is_secure =  $request->server('HTTPS') === 'on'
            || $request->server('HTTPS') === '1'
            || $request->server('SSL') === 'on'
            || $request->server('HTTP_X_FORWARDED_PROTO') === 'https'
            || $request->server('HTTP_CLOUDFRONT_FORWARDED_PROTO') === 'https'
        ;   // ※後述1

        if (! $is_secure) {
            return $next($request);
        }

        URL::forceScheme('https'); // ※後述2

        Request::setTrustedProxies([
            '0.0.0.0/0'
        ]); // ※後述3

        return $next($request);
    }
}
  1. 大抵のProxy環境下ではHTTP_X_FORWARDED_PROTOでいけるはずだけど、
    CloudFront経由時は更にHTTP_CLOUDFRONT_FORWARDED_PROTOを見ないとダメだった。
    HTTPSかどうかの判定は他にもあるかも。
  2. UrlGenerator::forceScheme() のエイリアス。これでUrlGeneratorが返すURLが(ほぼ)httpsになる。
  3. 上記2の例外の対処で一部のURLを生成する関数は、
    forceScheme関係なくSymfonyのRequestオブジェクトからURLを生成している。
    そやつは $_SERVER['REMOTE_ADDR']とここでセットしたIPを判定しマッチしてればhttps、
    そうでなければ$_SERVER['HTTPS']のみを見てスキーマを返している。
    (正しくIP指定しないとローカル開発時の直HTTPSアクセスでProxy経由と判定されちゃうよ☆)

ミドルウェアの登録

あとは普通にミドルウェア登録。

app/Http/Kernel.php
<?php
namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        /* ~~ その他ミドルウェア ~~ */
        AppHttpMiddlewareSecureAccess::class,
        /* ~~ その他ミドルウェア ~~ */
    ];
}

所感

  • forceScheme 強制スキーマ(強制とは言ってない)
  • SymfonyのRequestクラスはホストはX_ORIGINAL_URLやらX_REWRITE_URLetc…見て大分がんばってるのに、
    スキーマだけはこういう判定方法にしてるのはなんか理由あるんかな?

続きを読む

AWSのSESでメールを受信してSNS経由でWEBサーバにHTTP(s)通信する

前準備
・メール受信に必要なドメインのMXレコードを設定
・メールを保存するS3バケットを作成、ポリシーを設定しておく
ポリシーは以下に書いてます。
http://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-permissions.html

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSESPuts",
            "Effect": "Allow",
            "Principal": {
                "Service": "ses.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::BUCKET-NAME/*",
            "Condition": {
                "StringEquals": {
                    "aws:Referer": "AWSACCOUNTID"
                }
            }
        }
    ]
}

SNSの設定
トピックを作成する

Subscriptionsで、HTTP通信したいURLを設定する

ARNを覚えておく

SESの設定
SESのEmail Receivingで受けるメアドを設定

AddActionでS3を選択
メールを保存するbucketを選択
SNS topicで、さっき作ったSNSを設定する

ここまでで手順は終わり。
メールテスト。
受けるメアドにメールを送信して、S3にメールが入る&サーバにアクセスが来ていたらOK。

続きを読む

CloudWatchの料金が高い

同僚に「CloudWatchの料金高くね?」と指摘される
Slack料金通知画面
image.png

なんか高い!
Billingを確認。
image.png

どうやら詳細モニタリングを1分間にしていたからっぽい

EC2画面でとりあえず全て無効化する。5分間隔だと無料なんですね。
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-cloudwatch-new.html

image.png

あとは見守るのみ

続きを読む

shell scriptでEC2インスタンス内から自分についているタグを取得する

ID=`curl -s http://169.254.169.254/latest/meta-data/instance-id`
TAG_VALUE=`aws ec2 describe-tags --filter "{"Name":"resource-id","Values": ["$ID"]}" --query 'Tags[?Key==`TAG_NAME`][Value]' --region ap-northeast-1 --output text`

autoscalinggroupのtagを付ける設定で起動直後は取れないことがあるのでwhileとかで回そう

続きを読む