AWS Route53とWAFのGeolocation機能を検証

WAFに地域を制限する新機能がでました。geolocationについて検証しました。ついでにroute53のgeolocationも検証しました。

1.route53のgeolocation検証

検証

アクセスをUS(アメリカ)だけに限定して、あらゆる方法で日本からのアクセスどこまで防いでくれるか検証

検証環境

・route53にて[test.example.com]と登録後geolocaionをUSに設定する。(ドメインは例です。)
・EC2静的webホストのインスタンスを作成
・EC2にEIPを付与
・Applicattion load blanceでインスタンスを紐付け

ALBのヘルスチェックを通して検証

結果(左アクセス方法と右結果)

ドメインアクセス————————–拒否(ドメインが見つかりませんでした。)
EC2のパブリックドメイン—————–アクセスできた
EC2のEIP————————————アクセスできた
ALBのドメイン—————————–アクセスできた
ALBのネットワークインターフェース—-アクセスできた

感想

当たり前ですがRoute53を通らない方法でやると通る。

2.WAFのGeoMatch検証

検証

US以外のアクセスすべて拒否設定で日本からアクセス検証
(上記のroute53で設定した。geolocationはもとに戻しています。)

検証環境

・EC2静的webホストのインスタンスを作成
・EC2にEIPを付与
・Applicattion load blanceでインスタンスを紐付け
・WAFGeoMatchをUS(アメリカ)以外全部拒否設定

結果(左アクセス方法と右結果)

ドメインアクセス————————–拒否(HTTPStatus:403)
EC2のパブリックドメイン—————–アクセスできた
EC2のEIP————————————アクセスできた
ALBのドメイン—————————–拒否(HTTPStatus:403)
ALBのネットワークインターフェース—-拒否(HTTPStatus:403)

感想

通常のアクセスする順番が「クライアント⇒route53⇒ALB(WAFアタッチ)⇒EC2」なので、WAFで拒否してくれるが、EC2に直接アクセスするとALBは通らないのでアクセス可能になってしまう。
本来であれば、WEBインスタンスを作るときにEC2に直接EIPとパブリックドメインはつけることがないので問題ないと思います。
地域のアクセス制限するのならWAFのほうが良い。

続きを読む

Elastic BeanStalkの環境別のタグ付を忘れたときにどうすんの?って話

1年ぐらい前からBeanStalk使ってます。
なんつうのか、この辺のAWSのマネージドサービスっていうの?ってほんとすごいですよね。
インフラエンジニアなんか必要なくなりますよね。
AWSの機能をガリガリ使って一昔前の3-tier systemなんて、ネットワークの知識もインフラ系のIeasの知識もなくアプリ屋さんがポチポチやってればいつでも復元できるシステム!が出来上がっちゃうんですもんね。
しかもはやりのECSつかったDockerコンテナの仕組みもつかって・・・
「あ〜〜Dockerとか最近ちゃってもう最近開発とか楽でさ〜〜、インフラ屋?いるの?仕事おせえよあいつら」とかほざけるわけです。

俺なんか無職まっしぐらです。

Beanstalkってなんぞや?

さて、まぁ今更ですがBeanStalkってなんぞやって人に簡単に説明しておきますと、
要は初見殺しとも言えるAWSの「CloudFomartion」を”比較的”わかりやすいUIで設定できるというマネージメントシステムです。
基本はネットワーク層+3tier+インスタンススケーリング何かをある程度コントロールしてくれます
更にアプリケーションのデプロイはソースをまとめておくとECR上にコンテナイメージとして作成してくれたり、そこまで必要ない人でもPHP用のAMIを使ってちょっとPHPで動作させたいコンテンツを作ってデプロイしてみたいな事ができます。
この辺、コンソールでポチポチやってると、たしかに時間的にそれなりのリソースを取られますしで開発したアプリケーションのバージョン管理もソレナリにしてくれるので何度も言いますがとても便利なサービスだと思っています。

BeanStalkのアレなところ

こんなに便利そうなBeanStalkなんですが、サーバサイド(アプリケーション開発者)にはなり針を振り気味なサービスなので、少しでも込み入ったことを始めるととたんに出来ないことが出てきます。
そこで、「.ebextentions」というファイルを作ってアプリ層のレポジトリに拡張設定をおいて設定を入れたりする訳です。
まぁ、デフォルトで設定されるCloudFormationの設定項目をアプリ層に置くようにするわけで、インフラ屋のいない会社なんかだと違和感ないというかとても理にかなっているようにみえるんですが、タスクをサーバサイド・インフラサイドと別けているチームなんかだと

インフラ屋がアプリ屋に土下座をしてネットワーク設定なんかを開発アプリレポジトリに入れてもらう

なんていう状態が発生してしまい、なんとなく自分の存在意義を感じられなくなってきたりもします。

そこで、インフラ屋さんは「EBとかつかってらんねぇよ、せめてCloudFormationでやろうぜ・・・(JSON書きたくないけど)」とか言い始めるわけです。

あ、個人的にはterraformに逃げましたw

本題の環境ごとのタグの調べ方について

なんか、閑話休題のボリュームがいつもまとまらず多めになってしまう訳ですが
本当に書きたかったEBで作った環境に対してのタグの後付とかに関してです。

EBで環境を作るとEBにおけるタグを設定しておけば、そのアプリケーションで起動したEC2インスタンスなどには一応Nameタグがついてくれてどの環境かわかりやすくなるのですが、SecurityGroup・ELB/ALBなどには最初にタグを付け忘れたりすると、どの環境で使っているSG・LBなのかを探すのが非常に困難になります。

具体的には例えばSecurityGroupなんかだと下記のようなタグが付くことになります
– awseb-a-3xxxxx2xxxx-stack-AWSEBSecurityGroup-HOGEFUGE1HOGE

まぁ訳わんないですよね。SGに関してはまだNameのタグを設定しておけば自分で任意に環境名をつけられますが
ELBとかだとName=「ユニークな文字列:DNS名」になっているのでAWSのコンソールやawscliコマンドの返り値だけでは一見どの環境用セット用の機材なのかが全くわからない状態になってしまっています。

また、タグ自体も起動段階で設定していないと、環境をすべてEB的に再構築しない限り(つまり環境内のすべてを捨ててもう一度構築し直す処理)を入れないと追加したNameタグなどは設定されない状況に陥ります。

これをあとでチクチクと手作業で入れることになるんですが、
– awseb-a-3xxxxx2xxxx-stack-AWSEBSecurityGroup-HOGEFUGE1HOGE

この文字列はEBの中身を見ているだけだと判別がつかない物なのです。
なぜか
この文字列自体がCloudformationで作られている物だからですwww。
なのでこのELBがどのEBアプリケーションで使われているのか?というのを調べるためには

  • まず、上記の文字列のうち、「awseb-a-3xxxxx2xxxx-stack」までの部分を抜き出し
  • CloudFormationのスタック一覧のFilterをその文字列に入れ
  • 説明欄にちゃんと記載されている「AWS Elastic Beanstalk environment (Name: ‘hogefuge-app’)」を確認するという作業が必要になります。

まぁ調べる方法がある分マシですがね・・・この辺もうデフォルトでNameダグとしてすべての環境に入れてしまってもいいと思うんですけどね

以上、ちょっとしたボヤキでした。
ちなみに、サーバサイドの方の「構築の手間が楽になるからEB使うことを強く推したい」という要望であるプロジェクトでEB使いましたがもう本サービス運用では二度と使いたくありません・・・・

続きを読む

ALBの概観図

AWSのALBの設定をするときに概念が混乱し始めて来てしまったので、全体の概念図をまとめようとおもいました。

今後、始めてALBを設定する人の参考になれば幸いです。

全体図

image.png

各設定に関してはAWSのドキュメントや様々な記事が参考になるので詳しい設定方法は省略します。

  1. Route53で、ドメイン名を、ALIASでALBと紐付ける。
  2. ALBのListenerのTargetGroupを設定します。
  3. TargetGroupに当該のEC2インスタンスを紐付けます。

セキュリティグループをつける

image.png

ALBと各EC2にそれぞれセキュリティグループをつけます。
ALB -> インバウンド:全て アウトバウンド:EC2のセキュリティグループ
EC2 -> インバウンド:ALBのセキュリティグループ インバウンド: 全て
にそれぞれ設定します

続きを読む

logmonとALBのdrainingでリロード攻撃(F5アタック)へのパッシブ対策

以下の記事で紹介した小ネタの、具体的な利用例です。

1. 対策の内容

リロード攻撃(F5アタック)への対策としては、前段のApacheにmod_dosdetectorやmod_evasiveを入れる、WAFを導入する等があります。
このように、前段ですべて対応できれば良いのですが、

  • リロード攻撃を行っているのが正規ユーザである
  • リクエストによって(また、ユーザ毎に処理に必要なデータ量の多寡によって)レスポンスタイムにばらつきがある

というような場合、なかなか前段だけでの対処は難しいのではないかと思います。
そこで、Webアプリケーションサーバ(Tomcatなど)の処理が詰まってしまったときに、サービスの完全停止を避けるためにWebアプリケーションサーバの再起動を行うことがあると思いますが、

  • 「処理が詰まった」といっても、詰まり始めのうちは「特定の遅い処理」だけが「詰まる」のであり、それ以外のリクエストに対する処理は正常にレスポンスを返すことができている
  • 正常なレスポンスを返すことができるリクエストまで、再起動で中断するのは(なるべく)避けたい

ということで、Apacheのgraceful restartのような処理をしよう…というのが今回の内容です。

※きちんとgracefulな処理をするためには、Webアプリケーションサーバは複数台必要です。

2. ポリシー・IAM Roleの準備

まずは、先ほどの記事にある通り、以下の作業を行います。

  • ポリシーを設定する
  • 設定したポリシーをEC2用IAM Roleにアタッチする
  • そのIAM RoleをEC2(Webアプリケーションサーバ)にアタッチする

なお、今回のケースでは、「ec2:DescribeInstances」に対する権限は不要ですので、この部分はカットしても良いでしょう。

3. EC2上の設定

以下の記事を参考に、EC2(Webアプリケーションサーバ)にlogmonを導入します。

logmon.confには、以下の内容を設定します。

  • 1行目 : 監視対象のログファイル(Apacheのエラーログなら「:/var/log/httpd/error_log」など)
  • 2行目 : 監視対象のキーワード(正規表現/Tomcatのレスポンスが返らない場合を拾うのなら「[error] (70007)The timeout specified has expired」にマッチする内容)
  • 3行目 : 先の記事に示されているとおり

続いて、最初の記事で紹介したスクリプト「aws_utils.sh」を配置し(私の例では「/usr/local/sbin/」内)、「get_instance_id()」の部分だけ以下の内容に置き換えます。

aws_utils.sh(変更部分のみ)
#######################################
# 自身のインスタンス ID を取得する
# Returns:
#   INSTANCE ID
#######################################
get_my_instance_id() {
  instance_id=`/usr/bin/curl http://169.254.169.254/latest/meta-data/instance-id`
  if [ -z "$instance_id" ]; then
    echo 'host not found'
    exit 2
  fi
  echo ${instance_id}
}

※draining(登録解除の遅延)時間の長さに合わせて「SLEEP」の秒数も調整します。

それから、crontabから一定間隔で呼び出すスクリプト(私の例では「/usr/local/sbin/check_count.sh」)を配置します。

check_count.sh
#! /bin/sh

# スクリプトをインポートする
. /usr/local/sbin/aws_utils.sh

# トリガ判定
if [ `cat /tmp/logmon_count` -ge 20 ]; then

  # 閾値越え -> logmonサービス停止
  /sbin/service logmon stop

  # 二重トリガ起動防止(countを0に)
  echo 0 > /tmp/logmon_count

  # ALBでターゲットグループから外す
  ALB_TARGET_GROUP_ARN=('arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/YYYY/zzzzzzzzzzzzzzzz' 'arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:targetgroup/YYYY/zzzzzzzzzzzzzzzz')
  INSTANCE_ID=$(get_my_instance_id)

  for arn in ${ALB_TARGET_GROUP_ARN[@]}
  do
    alb_deregister ${arn} ${INSTANCE_ID}
  done

  # ALBでdraining完了待ち
  for arn in ${ALB_TARGET_GROUP_ARN[@]}
  do
    alb_waiter ${arn} ${INSTANCE_ID} 'unused' > /dev/null
  done

  # Webサービス停止
  /sbin/service tomcat8 stop

  /bin/sleep 10

  # サービス起動
  /sbin/service tomcat8 start

  /bin/sleep 10

  /sbin/service logmon start

  # ALBでターゲットグループに戻す
  for arn in ${ALB_TARGET_GROUP_ARN[@]}
  do
    alb_register ${arn} ${INSTANCE_ID}
  done

  # ターゲットグループに戻ったことを確認する
  for arn in ${ALB_TARGET_GROUP_ARN[@]}
  do
    alb_waiter ${arn} ${INSTANCE_ID} 'healthy' > /dev/null
  done

fi

# countを0に
echo 0 > /tmp/logmon_count

if文の「20」は閾値です。適切な値に調整してください。
この例ではWebアプリケーションサーバとしてTomcat8を使っていますが、適切なものに置き換えてください。Tomcatの場合は、draining前後でPrintClassHistogramの出力などもしておくと良いです。

また、この例ではEC2(Webアプリケーションサーバ)を複数のターゲットグループ(配列「ALB_TARGET_GROUP_ARN」)に登録しています。
それぞれのターゲットグループでdraining時間が違う場合は、時間が短いものを先に記述すると良いです(後述の通りログを記録する場合は特に)。
1つの場合は配列にせず、for文で回す必要もありません。

なお、この例ではログを /dev/null に捨てていますが、実際に使うときにはきちんとログファイルに記録しておいたほうが良いです(時刻などとあわせて)。

最後に、このスクリプトを、実行ユーザ(rootなど)のcrontabに登録します(私の例では1分間隔で実行⇒閾値は1分当たりのカウントに対して設定)。このとき、1行目に「SHELL=/bin/bash」を挿入しておきます。

crontab登録例
SHELL=/bin/bash

*/1 * * * * /bin/sh /usr/local/sbin/check_count.sh

設定できたら、カウントファイル(私の例では「/tmp/logmon_count」)に閾値以上の値を書き出して、正しくdraining→ターゲットから削除→Webアプリケーションサーバ再起動→ターゲットに登録が行われるか、確認します。

テスト
echo 20 > /tmp/logmon_count

4. 注意点

drainingすると再起動には時間が掛かるので、Webアプリケーションサーバは最低でも4台程度は必要です。

続きを読む

ALB・EC2小ネタ/AWS CLIでALB配下のTGにEC2インスタンスを登録/削除するためのポリシー(メモ)

kakakakakkuさんが以下のブログ記事で公開されているALBのターゲットグループにEC2インスタンスを登録/削除するシェルスクリプトを実行するために必要なポリシーです。

1. 指定するポリシー

以下のポリシーを登録し、スクリプトを動かすEC2インスタンスに付与するIAM Roleにアタッチします。

AlbRegistrationPolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:RegisterTargets"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Resource": "*"
        }
    ]
}

特に何のひねりもないですが、AWSユーザーガイドの以下のページにある通り、

  • ALBの3つのAPIアクション
  • EC2の1つのAPIアクション(これはkakakakakkuさんの記事中「aws_utils.sh」の「get_instance_id()」で使用)

とも、リソースレベル権限の詳細な指定に対応していないので、Resourceには「*」を指定する必要があります(最初見落としていて、AWSパートナーさんに教えていただきました)。
本番(プロダクト)環境とステージング/開発環境を同一のアカウントで運用している場合は、指定するターゲットグループのARNを間違えないよう注意が必要です。

2. おまけ1

私の場合、登録/削除するEC2インスタンス自身でAWS CLIのスクリプトを実行しているので、前述の「get_instance_id()」は使わず、curlで http://169.254.169.254/latest/meta-data/instance-id をGETしてインスタンスIDを取得しています。
その場合は「ec2:DescribeInstances」の権限は不要です。

3. おまけ2

crontabから呼び出すスクリプトの場合、初期設定のままでは「.」や「source」で「aws_utils.sh」をインポートできないので、1つのスクリプトにまとめるか、以下の記事を参考にしてインポートできるようにします。

続きを読む

OpsWorks (Chef 11.10) 内のカスタムレシピでaws-sdk-v2を使う方法

背景

OpsWorks (Chef 11.10) でカスタムレシピを利用しようとすると、aws-sdkのバージョンがv1のため、最近のサービスをAPIから利用できませんでした。
以下、v2を利用するための手順です。

手順

aws-sdk-v2をgem install

具体的には以下のカスタムレシピをまず実行しときます。

install_aws_sdk_v2.rb
chef_gem "aws-sdk" do
  version "2.10.55"
  action :install
end

利用するときにaws-sdk-v2をrequire

aws-sdk で require してしまうと、v1が読み込まれてしまいます。
v2を利用するためには、 aws-sdk-resources を指定すれば良いです。
以下はv1では利用できなかったALB連携の例。

require 'aws-sdk-resources'

region = node["opsworks"]["instance"]["region"]
ec2_instance_id = node["opsworks"]["instance"]["aws_instance_id"]

client = ::Aws::ElasticLoadBalancingV2::Client.new(region: region)

target_to_attach = {
  target_group_arn: "xxxx",
  targets: [{ id: ec2_instance_id }]
}

client.register_targets(target_to_attach)

これでv2を利用できました。

続きを読む

NLBでfluentdのforwardパケットを分散させてみた

AWSの新しいロードバランサであるNLB(Network Load Balancer)を使ってfluentdのforwardパケットを分散してみたので、レポートをまとめておく。

NLB自体については、クラスメソッドのブログ等で紹介されているのでそちらを参照するのが分かり易い。

静的なIPを持つロードバランサーNetwork Load Balancer(NLB)が発表されました!
試してわかった NLB の細かいお作法

ざっくり言うと、TCPプロトコルを対象にしたALBって感じ。
ターゲットグループはポートレベルで設定できるので、コンテナ環境と相性が良い。
ポート違いで複数fluentdが立っていても同じグループとしてまとめて分散できる。

利用までの流れ

  1. ターゲットグループを作成し、TCPレベルでコネクションが貼れるかのヘルスチェックの設定をする
  2. インスタンスもしくは対象IPと、ポートの組をターゲットグループに登録する
  3. NLBを作成し対象となるAZを設定、リスナーポートとターゲットグループを紐付ける
  4. DNSを登録してそのDNS向けにforwardパケットを流す様にする

fluentdで利用する場合

普通、fluentdのforwardパケットはクローズドなネットワークを流れるので、internal NLBとして作成した。
この時、NLBはEIPを紐付けられる様になっているので、必要があればEIPを準備する。その場合AZ毎に必要になる。後からの変更はできないらしい。
EIPを紐付けない場合は自動でプライベートIPが採番される。
ヘルスチェックパケットは、NLBのIPからやってくる。
NLBはセキュリティグループを持つことができないので、ヘルスチェックを通過するにはノード側のセキュリティグループでNLBのIPから対象ポートへの通信を許可する必要がある。
VPCのレベルで通信を許可して問題になることはあんまり無さそうなので、VPCのCIDRレベルで通信を許可しておくのが楽だろう。

エントリポイントとしてDNS名が自動で生成されるので、そこかもしくはEIPを対象にしたDNSのレコードを作っておく。
どうもALIASレコードには対応していない様なので、NLBのDNS名を登録する場合はCNAMEレコードになる。
一回作ろうとして失敗した記憶があるんですが、さっき試してみたらALIASレコード作れました。

実際に通信する時の動きについて

forwardの送信元になるノードはNLBのIPに対してTCP接続している様に見える。
forwardを受信するノードは送信元のノードのIPからTCP接続されている様に見える。
コネクションは長時間に渡って維持できるので、どちらかの要求で再接続されない限りは同じコネクションをずっと使い回すことができる。
動作を見るにProxyはせずに、IPの送信先だけを書き換えてパケットフローをコントロールしているっぽい。
しかし、これでは送信側と受信側でIPが噛み合わないので、戻りパケットがどうやって戻ってきているのか良く分からん。
送信側と受信側でtcpdumpを使ってパケットをキャプチャした結果、送信側はNLBとやり取りしている様に見えてるし、受信側は各個別のノード宛にパケットを送っている。
受信側のルーティングテーブルを弄ることなく繋がっているので、VPC内のスイッチで何らかの魔法が行われていないと戻りパケットがNLBに戻らないと思うのだが、この理解で合ってんのかな。
まあ、VPCとはいえ内部では色々とスイッチレイヤーを経てインスタンス同士が通信しているだろうし、そういう事は出来そうだけどちょっと気持ち悪いw

health checkについて

NLBからのhealth checkはTCPで疎通できれば通過するので、実際にfowardパケットが届くかどうかまでは分からない。
out_forwardプラグインのhealth checkは0.14系からはtransportモードがデフォルトになっていて実際に通信を行うコネクションをそのまま利用してheartbeatを送っている。
デフォルトだと基本的にはTCPのコネクションをそのまま利用するはずなので、ちゃんとheartbeatの疎通が通る。
もしノードが死んだ場合、即座にheartbeatパケットが反応してconnectionが切断される。(この辺からもTCPレベルでは直結してる様に見える)
再接続する際は、実際にout_forwardのheartbeatパケットが届く所に対して接続されるので、すぐに生きてるノードに対してのコネクションが復活する。
LBを噛ました時の動作としては望ましい感じがする。

パフォーマンスについて

今のところ最大で40Mbpsぐらいの流量でパケットを受け取っている。
すごい大規模なサービスではないので、そんなに大した量ではない。
アクティブなコネクション数は、2箇所のAZを合計して200前後。
LBのCapacityUnitは大体0.3ぐらいを推移している。
パケットの分散具合も安定している。
当分は使っていけそう。
面倒だったので実際のレイテンシまでは測定してない。まあうちぐらいだと問題になることは無さそう。
弊社の流量では限界があるので、もっと大規模なトラフィックが流れている環境でのレポートがあると良さそう。

続きを読む