ORNEW

AWSアクセスキーをなるべく安全に利用しよう

Facebook にシェア
Pocket

開発者がAWSを利用するとき、おそらくマネジメントコンソールではなくプログラムから操作することが多いと思います。インタラクティブシェル上であればAWS CLI、Pythonプログラムからであればboto3を使うことが多いのではないでしょうか。

プログラムからAWSを操作する場合は、マネジメントコンソールとは異なり、アクセスキーを発行する必要があります。クライアントは、発行されたアクセスキーと秘密鍵でAWSの各リソースを操作することができます。

そのアクセスキー、安全ですか?

アクセスキーを用いたプログラムによるAWS操作はとても便利ですが、適切な運用をしなければアクセスキーを悪用されてしまうかもしれません。

今更な話に聞こえるかもしれませんが、クラウド破産のリスクは数年前から全く変わっていません。潜伏して個人情報を盗むのではなく、仮想通貨のマイニングを目的とした攻撃が年々増加している点から、むしろ多額の請求が来る危険性は高くなっているとすら言えるかもしれません。

特に、個人でAWSアカウントを使っている人は、杜撰なアクセスキー管理をしがちです。もしAWSのフルアクセスを許すような永続アクセスキーが流出して、勝手にGPUインスタンスで仮想通貨マイニングでもされた日には、本当に破産しかねません。しかしそういった「アカウントハッキング、アクセスキー流出によるマイニング被害」のニュースは決して珍しい話ではないのです。軽く検索するだけでもたくさんの記事が見つかります。

クラウドが普及して便利になった今だからこそ、アクセスキーの扱いが安全なのか再確認しましょう。

アクセスキーを少しでも安全に使うには?

アカウント管理やアラート設定など、セキュリティ上大事なことはたくさんありますが、ここではアクセスキーの管理について考えましょう。

基本的な話として、アクセスキーを第三者に渡してはいけません

このあたりはあまりにも当然の話なので、ここまでにします。問題は、「必要なアクセスキーを適切に運用するにはどうしたらいいか」です。

アクセスキーも所詮文字列に過ぎませんし、絶対に漏れないという保証はできませんが、流出時の被害を抑えるための対策はできます。

  1. IAMユーザのアクセス権限を制限する
  2. ロールを使う
  3. MFA認証をしないと使えないIAMユーザで短命のセキュリティトークンを発行する

最も簡単に実施できるのは、「実行するIAMユーザの権限を制限する」ことです。例えば、EC2を操作できないIAMユーザのアクセスキーなら、流出してもEC2を操作されることはありません。マネジメントコンソールからWeb上で簡単に制限をかけられるので、これは実行している方も多いかと思います。ただし、制限されたリソースは自分も使うことができないので、制限を絞るのにも限界があり、被害範囲を制限する以上の効果はありません。

最も堅牢なのは「ロールを使用する」ことです。ただし、ロールの委任権限を持つアカウントで逐一認可するか、認証と認可を自動化してロールを委任する仕組みを構築するなどの工夫が必要です。実施コストが高いですが、ロールの使用は安全性が高く柔軟性があります。

もう一つ堅牢な手段として、「IAMユーザにMFA認証を強制し、短期間だけ有効なセキュリティトークンを発行する」方法です。これは、開発段階で一時的にアクセスキーが必要なときに特に有効です。MFA認証を通過したときしかサービスにアクセスできないようにすることで、アクセスキー流出のインパクトが大きく軽減されます。しかし、MFA認証を強制すると、そのままアクセスキーでセッションを作成してもMFA認証を通過していないため、自分でもリソースにアクセスできません。この場合、Amazon Security Token Service(STS)を使うことで、有効期限の短いセキュリティトークンが発行できます。このセキュリティトークンを用いることで、IAMポリシーでMFA認証を強制したリソースにアクセスできます。

上記3つの方法を解説します。

IAMユーザ作成

アクセスキー以前の基本の話になりますが、ルートアカウントでのAWS利用、特にアクセスキー発行はしてはいけません。ルートアカウントが乗っ取られた場合、自分の手では対処できない事態に発展しかねません。

おそらく皆さんIAMユーザの作成はしているはずなので、その手順は省略いたします。重要なのは、IAMユーザに与えるアクセス権限ポリシーの設定になります。

ここに個人アカウントでありがちなIAMポリシーの一例があります。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IGotEverything",
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}

これではルートアカウントと大して変わりません。必要なリソースだけ許可するように変更しましょう。ここでは例としてS3のList操作だけを許可するポリシーを定義します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OnlyListS3AllBuckets",
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:ListBucket",
        "s3:HeadBucket",
        "s3:ListObjects"
      ],
      "Resource": "*"
    }
  ]
}

MFA強制ポリシー

IAMユーザにMFA認証を強制するようにしましょう。新しくIAMポリシーを作成します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAccessOwnProfile",
      "Effect": "Allow",
      "Action": [
        "iam:UpdateLoginProfile",
        "iam:UploadSigningCertificate",
        "iam:DeleteAccessKey",
        "iam:GetSSHPublicKey",
        "iam:UpdateAccessKey",
        "iam:DeleteSSHPublicKey",
        "iam:UpdateSSHPublicKey",
        "iam:ListSigningCertificates",
        "iam:CreateAccessKey",
        "iam:CreateLoginProfile",
        "iam:ListSSHPublicKeys",
        "iam:DeleteSigningCertificate",
        "iam:UploadSSHPublicKey",
        "iam:UpdateSigningCertificate",
        "iam:GetLoginProfile",
        "iam:DeleteLoginProfile",
        "iam:ChangePassword",
        "iam:ListAccessKeys"
      ],
      "Resource": "arn:aws:iam::*:user/${aws:username}"
    },
    {
      "Sid": "AllowListOwnMFADevices",
      "Effect": "Allow",
      "Action": "iam:ListMFADevices",
      "Resource": [
        "arn:aws:iam::*:mfa/*",
        "arn:aws:iam::*:user/${aws:username}"
      ]
    },
    {
      "Sid": "AllowAccessOwnMFADevice",
      "Effect": "Allow",
      "Action": [
        "iam:DeleteVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:ResyncMFADevice",
        "iam:CreateVirtualMFADevice"
      ],
      "Resource": [
        "arn:aws:iam::*:mfa/${aws:username}",
        "arn:aws:iam::*:user/${aws:username}"
      ]
    },
    {
      "Sid": "AllowOwnDeactivateMFADevice",
      "Effect": "Allow",
      "Action": "iam:DeactivateMFADevice",
      "Resource": [
        "arn:aws:iam::*:mfa/${aws:username}",
        "arn:aws:iam::*:user/${aws:username}"
      ],
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    },
    {
      "Sid": "DenyAccessUnlessSignedInWithMFA",
      "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"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

上記ポリシーを適用すると、MFA認証をしていない場合はMFAの登録に関する操作以外ができなくなります。強制的にMFAを有効化することになります。

ちなみに、MFAを強制するチュートリアルがIAMのドキュメントにあります。こちらも参考にしてください。

アクセスキーの発行

MFAを強制したIAMユーザを作成したら、アクセスキーを発行します。コマンドでも発行できますが、セキュリティ的な観点ではアクセスキーの発行権限を持ったアクセスキーを発行するべきではないため、マネジメントコンソールから作成することを推奨します。

プログラムからMFA認証を行う

aws configureを使ってプロファイルを設定します。本題とは関係ありませんが、--prifile引数にプロファイル名を指定すると、複数のキー管理のとき便利です。

$ aws configure --profile 'some-profile'
AWS Access Key ID [None]: xxxxxxx
AWS Secret Access Key [None]: xxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: json

なお、aws configureでの設定では~/.aws/credentialsにキーが保存されます。直接ファイルを作成・編集することもできますが、その場合はパーミッションが600になっている事を確認しましょう。不適切なパーミッションは流出のリスクがあります。

今回はPythonプログラムからの操作を例にします。pip install boto3でboto3をインストールし、セッションを作成する場合、通常は以下のようにします。

import boto3

# デフォルトプロファイル
session = boto3.Session()

# プロファイル名の指定が可能
session = boto3.Session(
    profile_name='some-profile')

# ハードコーティングは可能だが、流出リスクが高い(例えばGitHubに公開してしまうなど)
session = boto3.Session(
    aws_access_key_id='xxxxxx',
    aws_secret_access_key='xxxxxxx',
    region_name='ap-northeast-1')

ここまでは一般的なプログラムです。しかし、ポリシーでMFA認証を強制しているため、このままでは何の操作もできません。試しに任意のS3バケットのファイル一覧を取得しようとしてみても、エラーになるはずです。エラーにならなかったら正しくポリシーが適用できていません。

# 試しにS3アクセス(some-bucketは既に作ってあるとします)
session.resource('s3').Bucket('some-bucket').objects.all() 

STSでセッショントークンの取得

Amazon Security Token Service(STS)を用いると、MFA認証を通して短期間有効なセッショントークンを発行できます。

AWS CLIの場合:

$ aws sts get-session-token \
>   --duration-seconds 1800
>   --serial-number 'arn:aws:iam::012345678910:mfa/user-name' \
>   --token-code 012345
{
    "Credentials": {
        "SecretAccessKey": "xxxxxx",
        "SessionToken": "xxxxxx",
        "Expiration": "2018-01-01T00:00:00Z",
        "AccessKeyId": "xxxxxx"
    }
}

Boto3の場合:

credentials = session.client('sts')\
    .get_session_token(
        DurationSeconds=1800, # 30分
        SerialNumber='arn:aws:iam::012345678910:mfa/user-name',
        TokenCode='012345',
    )['Credentials']
{u'AccessKeyId': 'xxxxxx',
 u'Expiration': datetime.datetime(2018, 1, 1, 0, 0, 0, tzinfo=tzutc()),
 u'SecretAccessKey': 'xxxxxx',
 u'SessionToken': 'xxxxxx'}

トークンコードは、登録したMFAデバイスで表示される6桁の数字です。

GetSessionTokenで取得したトークンは、900秒(15分)〜129600秒(36時間)の間で秒単位に有効期間をしていできます。デフォルトは43200秒(12時間)の間のみ有効です。なるべく短く設定しましょう。開発中であれば、朝9時に有効時間12時間で発行すればその日は十分なはずです。なお、ここで発行したトークンは個別に無効化できません。無効化する場合は、発行者のIAMユーザのセッションを全て拒否する必要があります。

取得したアクセスキー、秘密鍵、セッショントークンから、新しいセッションを作成することで、本来の権限にアクセスできます。

session = boto3.Session(
    aws_access_key_id=credentials['AccessKeyId'],
    aws_secret_access_key=credentials['SecretAccessKey'],
    aws_session_token=credentials['SessionToken'],
    region_name='ap-northeast-1')

# 試しにS3アクセス
bucket = session.resource('s3').Bucket('some-bucket')
keys = [obj.key for obj in bucket.objects.all()]
print(keys)

このセッショントークンが盗まれたとしても、有効期限が切れればそれ以上の不正アクセスは不可能であり、被害はそこで止まります。また、物理的なMFAデバイスまたは別の端末を使った仮想MFAデバイスを用いることで、第三者がセッショントークンを不正に発行するリスクを軽減できます。セッショントークンが発行できなければ何もできないため、アクセスキーが流出しても即座に大きな被害にはなることはありません。

ロールの使用

ロールは、セッショントークンよりも柔軟で安全に一時的なアクセス権限を与えることができます。大きな違いは以下のとおりです。

ロールのユースケースは大きく以下の4つほどが挙げられると思います。

  1. アカウントを跨いだ権限の委任
    • 複数のアカウントを所持している際にアカウント間でリソースを共有する場合など
  2. 第三者へのアクセス権限の委任
    • 自社のCloudTrailログへのアクセスを提携先に許可する場合など
  3. AWS上のサービスに実行権限として与える
    • Lambdaの実行時の権限を指定する場合など
    • この場合はロールを引き受けるのはユーザ側ではなくAWS側
  4. 外部のIDとフェデレーションする場合
    • 外部のID(ActiveDirectoryやSAMLなど)とパーミッションの連携を図る場合など
    • もう少し詳しく言えば、「社内に認証システムあるのに更に別でAWSの認証情報を管理するの面倒くさくない?」という時に、外部認証を通過した際に一時的な認証情報を与える形でフェデレーションを行うことができる

個人のアカウントであれば、前項のセッショントークンで十分なケースがほとんどです。一方で、企業レベルの規模での運用では、ロールの適切な運用は必要不可欠と言えます。

ロールの引受方法はセッショントークンの取得とほぼ同じなので割愛します。

アクセスキーを上手く使おう

ここまでAWSのアクセスキーの安全な方法について再確認しました。簡単にまとめます。

勝手にマイニングされて破産しないように、アクセスキーは適切に運用しましょう!

参考資料