AWS ECS上にErlang(Elixir) Clusterを組むためのツールを作った

PhoenixをDockerに乗せてAWS ECS上で運用している. PhoenixはPubSubの機構を持っているが,このバックエンドは,デフォルトではPG2というErlangクラスタ内のプロセス間通信を前提にしたものになっている.

ECSのようなDockerクラスタ上にPhoenixを載せる際は,たいていこの部分をPG2ではなくRedis等にして,Erlangクラスタは構成できないがRedisを介してPubSubのメッセージがやり取りできる状態を作ったりする.

ただ,Redisバックエンドはいまいち安定しないという噂と,そもそもErlangVM自身がそういう機能を持っており,バックグラウンドプロセスを分散できるのであればそれを活用したくなるものである.

というわけで,あえてErlangクラスタを作りたくなった.

続きを読む

IAMユーザにMFAを強制しつつterraformする

AWSのIAMユーザで,コンソールログインする際には必ずMFAを強制したい,と思うことがある.

というわけでそういうIAM Policyを作って当ててみた.

IAM Policyでの強制

だいたいこういうのを参考にすると,そのまま使えるのがある.

blog.vtryo.me

ただし,注意すべきことがある. 最後の,

{
  "Sid": "BlockMostAccessUnlessSignedInWithMFA",
  "Effect": "Deny",
  "NotAction": [
      "iam:CreateVirtualMFADevice",
      "iam:DeleteVirtualMFADevice",
      "iam:ListVirtualMFADevices",
      "iam:EnableMFADevice",
      "iam:ResyncMFADevice",
      "iam:ListAccountAliases",
      "iam:ListUsers",
      "iam:ListSSHPublicKeys",
      "iam:ListAccessKeys",
      "iam:ListServiceSpecificCredentials",
      "iam:ListMFADevices",
      "iam:GetAccountSummary",
      "sts:GetSessionToken",
      "iam:CreateLoginProfile",
      "iam:ChangePassword"
  ],
  "Resource": "*",
  "Condition": {
      "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
      }
  }
}

ここで,ConditionBoolIfExists を使っている.

この条件式の詳細は以下の通り.

docs.aws.amazon.com

aws:MultiFactorAuthPresent に関して,

Deny、BoolIfExists、false のこの組み合わせは、MFA を使用して認証されないリクエストを拒否します。具体的には、MFA を使用しないで一時的認証情報を使用して行われたリクエストを拒否します。また、IAM ユーザーアクセスキーなどの長期的認証情報を使用して行われたリクエストも拒否します。*IfExists 演算子は、aws:MultiFactorAuthPresent キーが存在するかどうか、MFA が使用されているかどうかを確認します。これは、MFA を使用して認証されないリクエストを拒否する場合に使用します。

との記述がある.

これは即ちアクセスキーを用いたアクセスも拒絶されることになる.大抵の場合,ローカルからterraform applyする際には,自分のIAMユーザのアクセスキーを使っていることが多い.

Deny,BoolIfExists,falseの組み合わせを使うと,そういったアクセスまですべて拒否することになる. また,あまりよい構成ではないが,たとえばアプリケーションからAWSのサービスを呼び出す際に,アクセスキーを使っているようなパターンがある場合,この条件に引っかかってしまう.

そもそもMFA強制のポリシーをシステムユーザやIAM Roleに充てることは,意図してやりたいことではないと思うが.

terraformが使える状態のポリシー

アクセスキーからのアクセスを許可するためには,

{
  "Sid": "BlockMostAccessUnlessSignedInWithMFA",
  "Effect": "Deny",
  "NotAction": [
      "iam:CreateVirtualMFADevice",
      "iam:DeleteVirtualMFADevice",
      "iam:ListVirtualMFADevices",
      "iam:EnableMFADevice",
      "iam:ResyncMFADevice",
      "iam:ListAccountAliases",
      "iam:ListUsers",
      "iam:ListSSHPublicKeys",
      "iam:ListAccessKeys",
      "iam:ListServiceSpecificCredentials",
      "iam:ListMFADevices",
      "iam:GetAccountSummary",
      "sts:GetSessionToken",
      "iam:CreateLoginProfile",
      "iam:ChangePassword"
  ],
  "Resource": "*",
  "Condition": {
      "Bool": {
          "aws:MultiFactorAuthPresent": "false"
      }
  }
}

こうする.

Deny 効果、Bool 要素、false 値のこの組み合わせは、MFA を使用して認証できるが認証されなかったリクエストを拒否します。このステートメントは、MFA の使用をサポートする一時的認証情報にのみ適用されます。このステートメントは、長期的認証情報を使用して行われたリクエスト、または MFA を使用して認証されたリクエストへのアクセスを拒否しません。ロジックが複雑で MFA 認証が実際に使用されたかどうかをテストしないため、この例は慎重に使用してください。

とあるので,アクセスキーによる認証は許可されている.

ただし,これでも,一部terraform planが失敗した.

原因は,EC2 Spot Fleetに関するterraform定義が,Access Deniedになるためであった.

上記のポリシーでは,アクセスキーによるアクセスが拒否されないのになぜ?

実は,Spot Fleetはユーザが直接操作を行うものだけではなく,

ユーザーの代わりにインスタンスの入札、起動、タグ付け、削除を行う権限をスポットフリートに付与するためのロールを作成し、それをスポットフリートのリクエストで指定する必要があります。

という記述がある.

そのため,上記のようなポリシーが,SpotFleetの操作を行うRoleの認証を拒絶している可能性が高い.

これに対しては,PrincipalTypeを検証することで,Denyを避けることができる.

PrincipalTypeに関する条件はここに,

docs.aws.amazon.com

PrincipalTypeの種別はここに書いてある. docs.aws.amazon.com

で,以下のようなPolicyを作る.

{
  "Sid": "BlockMostAccessUnlessSignedInWithMFA",
  "Effect": "Deny",
  "NotAction": [
      "iam:CreateVirtualMFADevice",
      "iam:DeleteVirtualMFADevice",
      "iam:ListVirtualMFADevices",
      "iam:EnableMFADevice",
      "iam:ResyncMFADevice",
      "iam:ListAccountAliases",
      "iam:ListUsers",
      "iam:ListSSHPublicKeys",
      "iam:ListAccessKeys",
      "iam:ListServiceSpecificCredentials",
      "iam:ListMFADevices",
      "iam:GetAccountSummary",
      "sts:GetSessionToken",
      "iam:CreateLoginProfile",
      "iam:ChangePassword"
  ],
  "Resource": "*",
  "Condition": {
      "Bool": {
          "aws:MultiFactorAuthPresent": "false"
      },
     "StringEquals": {
          "aws:PrincipalType": ["account", "user"]
      }
  }
}

Conditionブロック内の複数の条件式はANDで効く.そのため,PrincipalTypeがaccount, userの場合 && MFAでない場合にのみ,Denyが発動するようになっている. 幸いにもSpotFleetのリクエストを行うRoleはaccountでもuserでもない.そしてコンソールからのログインはuserに該当する.

ただ確かにAWSのドキュメントに書いてあるとおり,複雑な認証フローになることは否めない.

注意点

最後に注意点を書いておくと,以上のようなPolicyをいじる際,普段terraform applyを行っているアカウント(例えば自分のアカウント)に対して,上記のようなPolicyをいきなり当ててしまうのは大変リスキーである. 例えば,最初に提示したような,アクセスキーからのアクセスを禁止するようなPolicyを当ててしまった場合,その後terraform applyは不可能になってしまうので,一度コンソールからログインして,自分のアカウントに付与されているPolicyを変更する必要がある.

また,うっかりするとログインすら拒絶するPolicyを当てることにも可能である.

そのため,こういうことをする場合は実験用アカウントを作成した上で,そのアカウントにのみPolicyを適用しながら,十分に検証した上で自分のアカウントにも付与しよう.