AWS CLI を使って ELB のセキュリティポリシーを最新に更新する

2016.04.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

aws-cli好きの菅野です。
今回のお話は、ELBで「HTTPS/SSLリスナー」を利用している方が対象となります。

「HTTPS/SSLリスナー」を利用する場合、SSLプロトコルや暗号化方式をパッケージ化した「事前定義されたセキュリティポリシー」を利用されていると思いますが、皆さんの中にはELBを作成した時に設定したままになっている方がいらっしゃるのではないでしょうか?
スクリーンショット 2016-04-27 10.38.17

今回はaws-cliを使ってELBのセキュリティポリシーを更新しましょうというお話です。

なぜ更新が必要なのか

SSLさえ使っていれば安心〜と思っている方がもしいらっしゃったらお気をつけください。
SSLに使われている暗号化方式も誰かが日々解読を試みていて、解読された場合は通信内容が盗み見できてしまいます。

AWSはこういった脆弱性に対し、解読された暗号化方式を外したり、より難解な暗号化方式を追加したパッケージを用意します。これが「事前定義されたセキュリティポリシー」と呼ばれるものになります。

ただし、AWSは最新のセキュリティポリシーを用意しますが勝手に適用はしません。勝手に適用すると、除外された暗号化方式を使っているサービスが使えなくなる為です。

そういった事情により、ELBのセキュリティポリシーの更新作業が必要となります。

AWSが用意しているセキュリティポリシー

AWSが用意してくる「事前定義されたセキュリティポリシー」は以下のページで公開されています。
Elastic Load Balancing での事前定義された SSL のセキュリティポリシー
この表にあるセキュリティポリシー名を取得するコマンドと結果は以下となります。

$ aws elb describe-load-balancer-policies --query "PolicyDescriptions[?PolicyTypeName=='SSLNegotiationPolicyType'].{PolicyName:PolicyName}" --output table
-----------------------------------------------
|        DescribeLoadBalancerPolicies         |
+---------------------------------------------+
|                 PolicyName                  |
+---------------------------------------------+
|  ELBSecurityPolicy-2015-05                  |
|  ELBSecurityPolicy-2015-03                  |
|  ELBSecurityPolicy-2015-02                  |
|  ELBSecurityPolicy-2014-10                  |
|  ELBSecurityPolicy-2014-01                  |
|  ELBSecurityPolicy-2011-08                  |
|  ELBSample-ELBDefaultNegotiationPolicy      |
|  ELBSample-OpenSSLDefaultNegotiationPolicy  |
+---------------------------------------------+

現時点では最新のセキュリティポリシーが一番上に表示されていますので、一番上のセキュリティポリシー名を取得します。(今回もjqをフル活用です)

$ aws elb describe-load-balancer-policies --query "PolicyDescriptions[?PolicyTypeName=='SSLNegotiationPolicyType'].{PolicyName:PolicyName}" | jq -r '.[0] | .PolicyName'
ELBSecurityPolicy-2015-05

まずは最新のセキュリティポリシー名「ELBSecurityPolicy-2015-05」を取得できました。

リスナーポートを取得する

セキュリティやスティッキーセッションのポシリーは、以下のようにリスナーと結びついています。

$ aws elb describe-load-balancers --load-balancer-name 【ELBの名前】 | jq -r '.LoadBalancerDescriptions[] | .ListenerDescriptions'
[
  {
    "Listener": {
      "InstancePort": 80,
      "SSLCertificateId": "arn:aws:iam::************:server-certificate/aaa.bbb.com",
      "LoadBalancerPort": 443,
      "Protocol": "HTTPS",
      "InstanceProtocol": "HTTP"
    },
    "PolicyNames": [
      "AWSConsole-LBCookieStickinessPolicy-********-*************",
      "AWSConsole-SSLNegotiationPolicy-********-*************"
    ]
  }
]

「LoadBalancerPort」がリスナーポート、「PolicyNames」が適用されているポリシー名となります。
複数のリスナーに対して同じリスナーポート(今回の場合は443)は使えないので、ポリシーはポートに結びついていると考えてOKです。
なのでリスナーポートの一覧を取得しておきましょう。コマンドと結果は以下となります。

$ aws elb describe-load-balancers --load-balancer-name 【ELBの名前】 | jq -r '.LoadBalancerDescriptions[] | .ListenerDescriptions[] | .Listener.LoadBalancerPort'
442
443
80

ここまでで、指定したELBの待ち受けポート番号の一覧が取得できました。

ポートに結びついているポリシーを取得する

リスナーポートが取得できましたので、次はそのポートに結びついているポリシーを取得します。
ポート番号443に結びついているポリシーを取得するコマンドと結果は以下となります。

$ aws elb describe-load-balancers --load-balancer-name 【ELBの名前】 | jq -r '.LoadBalancerDescriptions[] | .ListenerDescriptions[] | select(.Listener.LoadBalancerPort == 443) | .PolicyNames[]'
AWSConsole-LBCookieStickinessPolicy-********-*************
AWSConsole-SSLNegotiationPolicy-********-*************

「LBCookieStickinessPolicy」というのがスティッキーセッションのポシリーです。これは今回の話とは関係ありませんが後で必要になります。
「SSLNegotiationPolicy」が、現在適用されているSSLネゴシエーションポリシー名です。内部で使われている名前なのでマネジメントコンソールには表示されないものです。

現在使用しているセキュリティポリシーを取得する

先ほど取得したポリシー名を使って、「事前定義されたセキュリティポリシー」のどれを現在使用しているか取得しましょう。コマンドは以下となります。

$ aws elb describe-load-balancer-policies --load-balancer-name 【ELBの名前】 | jq -r '.PolicyDescriptions[] | select(.PolicyName == "AWSConsole-SSLNegotiationPolicy-********-*************") | .PolicyAttributeDescriptions[] | select(.AttributeName == "Reference-Security-Policy") | .AttributeValue'
ELBSecurityPolicy-2011-08

このポートに結びついているのは「ELBSecurityPolicy-2011-08」という名前のセキュリティポリシーだとわかりました。
最新は「ELBSecurityPolicy-2015-05」ですから、かなり古いセキュリティポリシーを利用している事になります。

ポリシーを作成する

古いセキュリティポリシーを使っている事がわかりましたので、これを最新に更新する必要があります。
まずはAWSが用意してくれている「ELBSecurityPolicy-2015-05」を使った「SSLNegotiationPolicy」を作成しましょう。作成するコマンドは以下となります。

$ aws elb create-load-balancer-policy --load-balancer-name 【ELBの名前】 --policy-name 【ポリシー名】 --policy-type-name SSLNegotiationPolicyType --policy-attributes AttributeName=Reference-Security-Policy,AttributeValue=ELBSecurityPolicy-2015-05

【ポリシー名】は、先ほど取得したポリシー名にprefix(この例では「cm-」)を付けたものがおすすめです。
cm-AWSConsole-SSLNegotiationPolicy-********-*************

ポリシーを適用する

「cm-AWSConsole-SSLNegotiationPolicy-********-*************」というポリシーが作成できましたので、次はそれをリスナーに結びつけます。コマンドは以下となります。

$ aws elb set-load-balancer-policies-of-listener --load-balancer-name 【ELBの名前】 --load-balancer-port 443 --policy-names AWSConsole-LBCookieStickinessPolicy-********-************* cm-AWSConsole-SSLNegotiationPolicy-********-*************

ここで注意して欲しいのが、「--policy-names」オプションの値にSSLネゴシエーションポリシーだけでなく、
スティッキーセッションのポシリーも渡している部分です。
ここでSSLネゴシエーションポリシーだけを渡すと、以下のように「維持設定」が無効になってしまいます。
スクリーンショット 2016-04-27 12.31.35
そこを間違えずに先ほどのコマンドを実行したら、マネジメントコンソールで確認してみましょう。
ELBのページを表示したままにしていた人は更新ボタンをクリックするのを忘れないでくださいね。
スクリーンショット 2016-04-27 12.36.46
最新のセキュリティポリシーに更新できました。
このELBを使っているサービスの動作確認も忘れないでくださいね。

今までのコマンドをスクリプト化する

今まで書いてきたコマンドを修正して実行する、それだけでは作業効率が良くなるどころか作業時間が長くなってしまいます。そこで作業時間を短縮し、効率良くする為にスクリプトを用意しました。(スクリプトはこのエントリーの最後に貼っておきます)

スクリプトを実行する

スクリプト実行すると、以下のような内容を出力します。
古いセキュリティポリシーを使っている場合はポリシーを作成する/適用する/元に戻すといった3つのコマンドも出力します。

-----------------------------------------------
|        DescribeLoadBalancerPolicies         |
+---------------------------------------------+
|                 PolicyName                  |
+---------------------------------------------+
|  ELBSecurityPolicy-2015-05                  |
|  ELBSecurityPolicy-2015-03                  |
|  ELBSecurityPolicy-2015-02                  |
|  ELBSecurityPolicy-2014-10                  |
|  ELBSecurityPolicy-2014-01                  |
|  ELBSecurityPolicy-2011-08                  |
|  ELBSample-ELBDefaultNegotiationPolicy      |
|  ELBSample-OpenSSLDefaultNegotiationPolicy  |
+---------------------------------------------+

最新の定義済みポリシー名:ELBSecurityPolicy-2015-05



-------------------------------------------------------
【ELBの名前】
-------------------------------------------------------

  ポート番号:442
  ポリシー名:ELBSecurityPolicy-2015-05

  ポート番号:443
  ポリシー名:ELBSecurityPolicy-2011-08
  ポリシーを作成するコマンド:aws elb create-load-balancer-policy --load-balancer-name ******** --policy-name cm-AWSConsole-SSLNegotiationPolicy-********-************* --policy-type-name SSLNegotiationPolicyType --policy-attributes AttributeName=Reference-Security-Policy,AttributeValue=ELBSecurityPolicy-2015-05
  ポリシーを有効化するコマンド:aws elb set-load-balancer-policies-of-listener --load-balancer-name ******** --load-balancer-port 443 --policy-names  AWSConsole-LBCookieStickinessPolicy-********-************* cm-AWSConsole-SSLNegotiationPolicy-********-*************
  ポリシーを元に戻すコマンド:aws elb set-load-balancer-policies-of-listener --load-balancer-name ******** --load-balancer-port 443 --policy-names  AWSConsole-LBCookieStickinessPolicy-********-************* AWSConsole-SSLNegotiationPolicy-********-*************

-------------------------------------------------------

ポート442のリスナーは最新の「ELBSecurityPolicy-2015-05」を使っていますが、ポート443のリスナーは「ELBSecurityPolicy-2011-08」という古いセキュリティポリシーを使っている事がわかります。
ポート443のリスナーのように、セキュリティポリシーの更新が必要な場合だけ「ポリシー作成/有効化/元に戻す」のコマンドが表示されます。
次にスクリプトが出力した「ポリシーを作成するコマンド」「ポリシーを有効化するコマンド」を実行して先ほどと同じようにマネジメントコンソールで確認してください。「ELBSecurityPolicy-2015-05」が適用されているはずです。

スクリプトを使う時の注意点

スクリプトは以下の注意点をご確認の上ご利用ください。

  • このスクリプトはELBの設定を書き換えるコマンドを出力します。テスト用のELBを作成して十分な動作確認をしてからお使いください。
  • このスクリプトにより出力されたコマンドを実行する前に、必ずコマンドの内容を確認してください。
  • このスクリプトでは自動で最新のセキュリティポリシー名を取得していますが、AWS側の仕様変更により並び順が変更された場合は古いセキュリティポリシーを使う可能性があります。必ず確認してください。

さいごに

いかがでしたでしょうか。
今回作成したスクリプトを使えば、一度設定したら忘れがちなELBのセキュリティポリシーの更新作業を効率化できると思います。
コマンドを表示する部分を修正すれば更新が必要な場合に自動で更新するようにもできますが、安全の為コマンドを表示するだけにしてあります。
自動で更新までしなくても、1週間に1回だけ実行して更新が必要な場合はSNSを使ってメールで通知するといった運用方法もいいのではないでしょうか。
今回作成したスクリプトが皆様のお役に立てれば幸いです。

参考ページ

これらのページを参考にさせていただきました。
ありがとうございました。
AWS CLI Command Reference - elb
AWS CLI を使用した SSL ネゴシエーション設定の更新
ELBによるSSL Terminationをご利用中の方へ、TLSの脆弱性「Logjam」対策のご案内
ELBのSSLセキュリティポリシーからRC4が除外されました

ELBのセキュリティポリシー更新コマンド作成スクリプト

#!/bin/sh

# デフォルトリージョンを指定
REGION="ap-northeast-1"

# アクセスキー等をexport
export AWS_ACCESS_KEY_ID=''
export AWS_SECRET_ACCESS_KEY=''
export AWS_DEFAULT_REGION=${REGION}

# 定義済みポリシー名リストを表示
echo
aws elb describe-load-balancer-policies --query "PolicyDescriptions[?PolicyTypeName=='SSLNegotiationPolicyType'].{PolicyName:PolicyName}" --output table

# 最新の定義済みポリシー名を取得して表示
LATEST_POLICY_NAME=`aws elb describe-load-balancer-policies --query "PolicyDescriptions[?PolicyTypeName=='SSLNegotiationPolicyType'].{PolicyName:PolicyName}" | jq -r '.[0] | .PolicyName'`
echo
echo "最新の定義済みポリシー名:"${LATEST_POLICY_NAME}

# elbのリストを取得
elb_list=`aws elb describe-load-balancers | jq -r '.LoadBalancerDescriptions[] | .LoadBalancerName'`

# 空行
echo

# 取得したelbのリストをループ
for elb_name in ${elb_list[@]}
do

  # 空行を表示
  echo
  echo
  echo "-------------------------------------------------------"

  # elb名を表示
  echo "【"${elb_name}"】"
  echo "-------------------------------------------------------"

  # 現在適用されているリスナーのポート番号のリストを取得
  port_list=`aws elb describe-load-balancers --load-balancer-name ${elb_name} | jq -r '.LoadBalancerDescriptions[] | .ListenerDescriptions[] | .Listener.LoadBalancerPort'`

  # ポートのリストでループ
  for port_num in ${port_list[@]}
  do

    # 現在適用されているセキュリティポリシーのリストを取得
    policy_name_list=`aws elb describe-load-balancers --load-balancer-name ${elb_name} | jq -r '.LoadBalancerDescriptions[] | .ListenerDescriptions[] | select(.Listener.LoadBalancerPort == '${port_num}') | .PolicyNames[]'`

    # 元のポリシー名
    old_policy_names=""

    # 新しいポリシー名
    new_policy_names=""

    # 新規作成するポリシー名
    create_policy_name=""

    # アップデートが必要な場合1を立てる
    update_flg=0

    # ポリシーのリストでループ
    for policy_name in ${policy_name_list[@]}
    do

      # 作成するポリシー名を作成
      create_policy_name="cm-"${policy_name}

      # 定義済みポリシー名を取得
      reference_policy_name=`aws elb describe-load-balancer-policies --load-balancer-name ${elb_name} | jq -r '.PolicyDescriptions[] | select(.PolicyName == "'${policy_name}'") | .PolicyAttributeDescriptions[] | select(.AttributeName == "Reference-Security-Policy") | .AttributeValue'`

      # 定義済みポリシー名が取得できたら
      if [ "${reference_policy_name}" != "" ]
      then

        # ポート番号を表示
        echo
        echo "  ポート番号:"${port_num}

        # 定義済みポリシー名を表示
        echo "  ポリシー名:"${reference_policy_name}

        # 定義済みポリシー名が最新じゃなかったら
        if [ "${reference_policy_name}" != "${LATEST_POLICY_NAME}" ]
        then
          update_flg=1
        fi

        # SSLNegotiationPolicyなら作成するポリシー名を追加
        new_policy_names=${new_policy_names}" "${create_policy_name}

      else

        # LBCookieStickinessPolicyなら新しいポリシー名にそのまま追加
        new_policy_names=${new_policy_names}" "${policy_name}

      fi

      # 元のポリシー名に追加
      old_policy_names=${old_policy_names}" "${policy_name}

    done

    # アップデートが必要だったら
    if [ ${update_flg} == 1 ]
    then

        echo "  ポリシーを作成するコマンド:aws elb create-load-balancer-policy --load-balancer-name ${elb_name} --policy-name ${create_policy_name} --policy-type-name SSLNegotiationPolicyType --policy-attributes AttributeName=Reference-Security-Policy,AttributeValue=${LATEST_POLICY_NAME}"
        echo "  ポリシーを有効化するコマンド:aws elb set-load-balancer-policies-of-listener --load-balancer-name ${elb_name} --load-balancer-port ${port_num} --policy-names ${new_policy_names}"
        echo "  ポリシーを元に戻すコマンド:aws elb set-load-balancer-policies-of-listener --load-balancer-name ${elb_name} --load-balancer-port ${port_num} --policy-names ${old_policy_names}"

    fi

  done

done


# 空行を表示
echo
echo "-------------------------------------------------------"