IAMユーザーのアクセスキー

Keeperシークレットマネージャーのローテーションを使用してAWSアクセスキーを自動的にローテーション

概要

本ページでは、Keeperシークレットマネージャーのローテーションの「NOOP モード」を使用して AWS IAMユーザーアクセスキーをローテーションする方法について解説します。「NOOPモード」は、Keeperレコード内で設定するフラグであり、これによりゲートウェイは主なローテーション方式を飛ばして、ボルト内のPAMユーザーレコードに添付されたポストローテーションスクリプトを直接実行するように指示します。

本ページには、要件、手順、Pythonスクリプトの例が含まれています。このスクリプトは、提供された管理者認証情報 (管理者アカウントのAWSアクセスキー) と EC2インスタンスロール認証の両方をサポートします。このスクリプトでは、以前のユーザーキーを削除するなど、アクセスキーを安全にローテーションします。ローテーションが完了すると、新しいキーはKeeperレコードに保存されます。

要件

  1. KSMロール: Keeperユーザーが、KeeperシークレットマネージャーおよびKeeperシークレットマネージャーローテーションへのアクセスできるロールに属していることを確かにします。

  2. Keeperゲートウェイを実行するLinuxインスタンス: ゲートウェイは、EC2インスタンス、任意のプライベートクラウド、オンプレミスにデプロイできます。このスクリプトは、ゲートウェイがEC2インスタンスで実行されている場合、EC2インスタンスロール認証を利用してアクセスキーのローテーションを実行できます。ゲートウェイが別の場所で実行されている場合、エンドユーザーのアクセスキーのローテーションを実行するには、ロール権限を持つアクセスキーを Keeperボルトに提供する必要があります。

ローテーションスクリプトのロジックフロー

1. 管理者認証情報の取得

スクリプトは、以下の3つの方法で管理者の認証情報を取得します。

  1. ポストローテーションスクリプトに直接添付されたレコード。

  2. ローテーション用に選択されたAWS PAM構成に提供されるアクセスキー。これは、ポストローテーションスクリプトに添付されたレコード (1で作成したもの) にアクセスキーが見つからない場合に使用されます。

  3. 上記のいずれの方法でも認証情報が提供されない場合は、AWSインスタンスロール認証を使用します。ゲートウェイは、EC2インスタンスロールが設定されたEC2インスタンス上で実行されている必要があります。

2. キーのローテーションロジック

このスクリプトは、delete_all_keys_before_rotatingカスタムフィールドに基づいて2つの操作モードをご利用になれます。

  • False (デフォルト) の場合、最初に新しいアクセスキーが作成され手から古いアクセスキーが削除され、新しく作成されたキーのみが保持されます。ユーザーアカウントにすでに2つのアクセスキーがある場合には失敗します。AWSではスクリプトによる3つ目のアクセスキーを作成できません。

  • このカスタムフィールドがTrueに設定されている場合、新しいアクセスキーを作成する前に、IAMユーザーの既存のアクセスキーをすべて削除します。エンドユーザーアカウントにすでに 2つのアクセス キーがある上記の状況で役立ちます。

3. ボルト内のKeeper PAMユーザーレコードの更新

キーのローテーション後、ローテーションされた PAMユーザーレコードは新しいAWSアクセスキーID、シークレットアクセスキー、作成日、削除されたアクセスキー IDで更新されます。

PAMユーザーレコードのフィールド要件

後ほどローテーションを設定するために、PAMユーザーレコードを作成する必要があります。以下のフィールドを作成する必要があります。

必須フィールド

フィールド名

説明

ログイン

アクセスキーをローテーションする必要があるAWSのユーザーアカウントの名前。

パスワード

この場合はダミー値になります。パスワードフィールドは自動的にローテーションし、使用されることはありませんが、必須フィールドです。

必要なカスタムフィールド

カスタムフィールドラベル

フィールドタイプ

説明

aws_access_key_id

テキスト

このフィールドには、ローテーション後に新しいアクセス キーIDが渡されます。

aws_secret_access_key

隠しフィールド

このフィールドには、ローテーション後に新しいシークレットアクセスキーが送信されます。

CreateDate

テキスト

このフィールドには、スクリプトによって新しいキーが生成された時点のタイムスタンプが含まれます。

Access_Key_ID_Removed_during_previous_rotation

テキスト

このフィールドには、ローテーション中にAWSのユーザーアカウントから削除された古いアクセスキーIDが含まれます。

delete_all_keys_before_rotating

テキスト

このカスタムフィールドはオプションです。「False」または「True」に設定でき、デフォルト値は「False」です。

「True」に設定すると、ローテーションスクリプトが、新しいアクセスキーを作成する前に、ユーザーアカウントの既存のアクセスキーをすべて削除します。これは、ユーザーがすでに2つのアクセスキーを設定している場合に必要となります。AWSではスクリプトによる3つ目のアクセスキーの作成が許可されていないため、新しいキーを追加する前に既存のキーを削除する必要があります。

NOOP

テキスト

このローテーションでは、ゲートウェイでローテーション スクリプトのみを実行し、組み込みのローテーション機能を使用してローテーションしないようにする必要があります。

値は以下となります。

True

Private Key Type

テキスト

NOOPを有効にする2番目のフィールド。

値は以下となります。

rsa-ssh

上記の詳細を使用してPAMユーザーレコードを手動で作成する代わりに、以下のcsvファイルをインポートすることもできます。これにより、必要に応じて修正および複製できるテンプレートレコードが作成されます。

ファイルをインポートすると、ログインレコードタイプが生成されますので、必ずPAM ーザーに変換してください。

Keeperボルトでのローテーションの設定

ゲートウェイがEC2インスタンスで実行されている場合、スクリプトに管理者アクセスキーを提供する必要はありません。ゲートウェイは、VMに割り当てられたAWSインスタンスロールの権限を活用します。

以下の手順では、最小限の権限でゲートウェイEC2インスタンスにEC2インスタンスロールを設定する方法について解説します。

アクセスキーをローテーションするための最小限の権限を持つインスタンスロールを追加/構成する手順

1. AWSでポリシーを作成

  1. IAM管理コンソールに移動します。

  2. [Policies] (ポリシー) を選択し、[Create policy] (ポリシーの作成) をクリックします。

  3. [JSON]を選択して以下を貼り付けます。ご利用のAWSアカウントIDに必ず置き換えてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:CreateAccessKey",
        "iam:ListAccessKeys",
        "iam:DeleteAccessKey"
      ],
      "Resource": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID_HERE:user/*"
    }
  ]
}
  1. ポリシーに名前を付けて保存します。

2. AWSでIAMロールを作成

  1. IAM管理コンソールに移動します。

  2. [Roles] (ロール) を選択し、[Create role] (ロールの作成) をクリックします。

  3. [AWS service] (AWSサービス) を選択し、[EC2]を選択します。

  1. 必要なIAMポリシー (上記で最小限の権限で作成したポリシー) を添付します。

3. EC2インスタンスにロールを割り当てる

  1. EC2マネジメントコンソールでインスタンスを見つけます。

  2. [Actions] (アクション) > [Security] (セキュリティ) > [Modify IAM role] (IAMロールの変更) をクリックします。

  1. 作成したロールを選択し、[Update IAM role] (IAMロールを更新) をクリックします。

  1. インスタンスロールの権限を確認する

ゲートウェイEC2インスタンスで以下のコマンドを実行すると、インスタンスロールにIAMと対話するための適切な権限があることを確認できます。

aws sts get-caller-identity

このロールを構成すると、EC2 インスタンス (この場合は Keeperゲートウェイ) はスクリプトに添付されたロール認証情報を自動的に使用するため、アクセスキー認証情報を必要とせずにIAMアクセスキーの作成や削除などを実行できるようになります。

ボルトからのローテーション構成

  1. ボルトに共有フォルダを作成すします。

  2. 上記のフィールドとカスタム フィールドを使用して、共有フォルダにPAMユーザーレコードを作成します。

  3. ゲートウェイがまだ存在しない場合は、Keeperボルトのシークレットマネージャータブでゲートウェイ用の新しいアプリケーションを作成します。

  4. アプリケーションに上記で作成した共有フォルダに対して編集権限があることを確かにします。

  5. EC2インスタンスでゲートウェイ (アプリケーションを選択した後のゲートウェイタブ) をプロビジョニングします。 EC2インスタンスで、Keeperボルトによって提供されるinstallコマンドを実行し、EC2インスタンスで以下のコマンドを実行することでboto3とkeeper_secrets_manager_coreがインストールされていることを確かにします。

pip3 install boto3
pip3 install keeper_secrets_manager_core
  1. Keeperボルトのシークレットマネージャータブで、PAM構成タブに移動します。必要に応じて、新しいPAM構成を作成します。

  2. [環境][ローカルネットワーク][AWS]を選択できます。 「AWS」を選択した場合は、「Access Key」と「Secret Access Key」フィールドを空白にしてください。これらを指定すると、インスタンスロール認証を使用せずに、スクリプトで自動的に使用されるようになります。AWS PAM構成にAWSアカウントIDを指定する必要があります。

  3. ゲートウェイを選択し、共有フォルダを選択して、PAM構成を保存します。

  1. 前述のPAMユーザーレコードを編集します。 

    • パスワードローテーション設定: 希望するスケジュールと上記で作成したPAM 構成を選択します。

    • レコードにPAMスクリプトを追加します。以下のファイルを選択し、スクリプトコマンドを指定してください。

python3

Python スクリプト

import boto3
import sys
import base64
import json
from keeper_secrets_manager_core import SecretsManager
from keeper_secrets_manager_core.storage import FileKeyValueStorage
from botocore.exceptions import ClientError, NoCredentialsError

# sys.stdinは配列ではないため、添字(例えば、sys.stdin[0])を使用することはできません。
for base64_params in sys.stdin:
    # ゲートウェイからパラメータを取得する
    params = json.loads(base64.b64decode(base64_params).decode('utf-8'))
    break

# Keeperでポストローテーションスクリプトに添付されたレコードを取得する
records = json.loads(base64.b64decode(params.get('records')).decode('utf-8'))

# ゲートウェイ構成を使用してKeeperシークレットマネージャーSDKを初期化する
secrets_manager = SecretsManager(config=FileKeyValueStorage('/etc/keeper-gateway/gateway-config.json'))

# ユーザーの新しいアクセスキーを作成する関数
def create_new_access_key(iam_client, user_name: str) -> tuple:
    new_key = iam_client.create_access_key(UserName=user_name)
    return new_key['AccessKey']['AccessKeyId'], new_key['AccessKey']['SecretAccessKey'], new_key['AccessKey']['CreateDate']

# ユーザーのアクセスキーをローテーションする関数
def rotate_user_access_key(iam_client, user_name: str) -> dict:
    if not user_name:
        raise ValueError("User name is required for rotation")

    try:
        # ステップ 1: ユーザーの新しいアクセスキーを作成する
        new_access_key_id, new_secret_access_key, create_date = create_new_access_key(iam_client, user_name)

        print(f"New Access Key created successfully for user {user_name}")

        # ステップ 2: ユーザーの既存のアクセスキーをすべて一覧表示する
        existing_keys = iam_client.list_access_keys(UserName=user_name)
        deleted_keys = [access_key['AccessKeyId'] for access_key in existing_keys['AccessKeyMetadata']
                        if access_key['AccessKeyId'] != new_access_key_id]

        # ステップ 3: 新しく作成したアクセスキー以外のすべてのアクセスキーを削除する
        for access_key_id in deleted_keys:
            iam_client.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)

        # 結果を返す
        return {
            'NewAccessKeyId': new_access_key_id,
            'NewSecretAccessKey': new_secret_access_key,
            'CreateDate': create_date,
            'DeletedAccessKeys': deleted_keys
        }

    except ClientError as e:
        print(f"An error occurred: {e}")
        raise e

# すべてのアクセスキーを削除し手からユーザーのアクセスキーをローテーションする関数
def delete_then_rotate_user_access_key(iam_client, user_name: str) -> dict:
    try:
        # ステップ 1: ユーザーの既存のアクセスキーをすべて一覧表示する
        existing_keys = iam_client.list_access_keys(UserName=user_name)
        deleted_keys = [access_key['AccessKeyId'] for access_key in existing_keys['AccessKeyMetadata']]

        # ステップ 2: すべてのアクセスキーを削除する
        for access_key_id in deleted_keys:
            iam_client.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)

        # ステップ 3: ユーザーのために新しいアクセスキーを作成する
        new_access_key_id, new_secret_access_key, create_date = create_new_access_key(iam_client, user_name)

        print(f"New Access Key created successfully for user {user_name}")

        # 結果を返す
        return {
            'NewAccessKeyId': new_access_key_id,
            'NewSecretAccessKey': new_secret_access_key,
            'CreateDate': create_date,
            'DeletedAccessKeys': deleted_keys
        }

    except ClientError as e:
        print(f"An error occurred: {e}")
        raise e

# ターゲットAWSアカウントでロールを引き受ける関数
def assume_role_in_target_account(role_arn, session_name='CrossAccountSession'):
    sts_client = boto3.client('sts')
    try:
        assumed_role = sts_client.assume_role(
            RoleArn=role_arn,
            RoleSessionName=session_name
        )
        credentials = assumed_role['Credentials']
        return {
            'aws_access_key_id': credentials['AccessKeyId'],
            'aws_secret_access_key': credentials['SecretAccessKey'],
            'aws_session_token': credentials['SessionToken']
        }
    except ClientError as e:
        print(f"Error assuming role: {e}")
        raise e

# ゲートウェイからの入力と、KSMを使用した完全なPAMユーザーのクエリ
pam_user_to_update = secrets_manager.get_secrets([params.get('userRecordUid')])[0]
user_name = params.get('user')

# 管理者ユーザーアカウントのアクセスキーを取得する
aws_admin_cred_provided = False
for record in records:
    if "role_arn" in record: # KeeperレコードからAssumeRole認証が検出
        aws_admin_cred_provided = True
        role_arn = record["role_arn"]
        aws_access_key_id = record["aws_access_key_id"]
        aws_secret_access_key = record["aws_secret_access_key"]
        print(f"Admin access key provided along with role arn for AWS AssumeRole. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
        # ロールを引き受けて一時的な認証情報を取得する
        assumed_role_credentials = assume_role_in_target_account(role_arn)
        # 引き受けたロールの認証情報を使用してIAMクライアントを初期化する
        iam_client = boto3.client(
            'iam',
            aws_access_key_id=assumed_role_credentials['aws_access_key_id'],
            aws_secret_access_key=assumed_role_credentials['aws_secret_access_key'],
            aws_session_token=assumed_role_credentials['aws_session_token'],
            region_name='us-east-1'
        )
        break
    if "aws_access_key_id" in record:
        aws_admin_cred_provided = True
        aws_access_key_id = record["aws_access_key_id"]
        aws_secret_access_key = record["aws_secret_access_key"]
        print(f"Admin access key provided. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
        # 管理者の認証情報を使用してIAMクライアントを初期化する
        iam_client = boto3.client('iam',
                                  aws_access_key_id=aws_access_key_id,
                                  aws_secret_access_key=aws_secret_access_key,
                                  region_name='us-east-1')
        break
    if "accessKeyId" in record:
        aws_admin_cred_provided = True
        aws_access_key_id = record["accessKeyId"]
        aws_secret_access_key = record["accessSecretKey"]
        print(f"Admin access key provided. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
        # 管理者の認証情報を使用してIAMクライアントを初期化する
        iam_client = boto3.client('iam',
                                  aws_access_key_id=aws_access_key_id,
                                  aws_secret_access_key=aws_secret_access_key,
                                  region_name='us-east-1')
        break
else:
    try:
        # Keeperシークレットマネージャーを使用してPAM構成から管理者アクセスキーを取得しようとする
        pam_config = secrets_manager.get_secrets([params.get('providerRecordUid')])[0]
        aws_access_key_id = pam_config.field('accessKeyId', single=True)
        aws_secret_access_key = pam_config.field('accessSecretKey', single=True)

        # キーが正常に取得されたか確認する
        if aws_access_key_id and aws_secret_access_key:
            aws_admin_cred_provided = True
            print(f"Admin access key retrieved from the PAM Config. Using Access Key ID {aws_access_key_id}, from the PAM Config.")
            # 管理者の認証情報を使用してIAMクライアントを初期化する
            iam_client = boto3.client('iam',
                                      aws_access_key_id=aws_access_key_id,
                                      aws_secret_access_key=aws_secret_access_key,
                                      region_name='us-east-1')
        else:
            print("Access key ID or secret key not found in the retrieved Keeper Secrets Manager record.")
    except Exception as e:
        # 取得中に発生したエラーを処理する
        print(f"Error retrieving admin access key: {e}")

# 管理者のアクセスキーが提供されていない場合、インスタンスロールの認証情報を確認する
if not aws_admin_cred_provided:
    print("No admin access key provided. Checking for instance role authentication...")
    try:
        # 認証情報なしでIAMクライアントを作成し、インスタンスロールの認証情報が利用可能かテストする
        iam_client = boto3.client('iam')
        iam_client.list_access_keys(UserName=user_name)  # APIにアクセスできるかテストする
        aws_admin_cred_provided = True
        print("Instance role credentials found. Using instance role authentication.")
    except NoCredentialsError:
        print("No instance role credentials available.")
    except ClientError as e:
        print(f"Error using instance role credentials: {e}")

# 認証情報が提供されていない場合、終了する
if not aws_admin_cred_provided:
    raise RuntimeError("No admin access key or instance role credentials provided. The script will exit.")

# ユーザーが`delete_all_keys_before_rotating`というカスタムフィールドを指定したか確認する
try:
    delete_all_keys_before_rotating = pam_user_to_update.custom_field('delete_all_keys_before_rotating', single=True)
    delete_all_keys_before_rotating = str(delete_all_keys_before_rotating).lower() in ['true', 'yes', '1']
except Exception as e:
    print(f"OPTIONAL field 'delete_all_keys_before_rotating' not found: {e}")
    delete_all_keys_before_rotating = False  # エラーが発生した場合、`false`に設定する

# ユーザーのパラメータに基づいて、適切なローテーション関数を呼び出す
if delete_all_keys_before_rotating:
    result = delete_then_rotate_user_access_key(iam_client, user_name=user_name)
else:
    result = rotate_user_access_key(iam_client, user_name=user_name)

if result:
    print("\nRotation Result:")
    print(f"New Access Key ID: {result['NewAccessKeyId']}")
    print(f"Create Date: {result['CreateDate']}")
    print(f"Deleted Access Keys: {result['DeletedAccessKeys']}")

    # KeeperのPAMユーザーレコードを更新する
    pam_user_to_update.custom_field('aws_access_key_id', result['NewAccessKeyId'])
    pam_user_to_update.custom_field('aws_secret_access_key', result['NewSecretAccessKey'])
    create_date_str = result['CreateDate'].isoformat() if hasattr(result['CreateDate'], 'isoformat') else str(result['CreateDate'])  # Convert the create_date to string format
    pam_user_to_update.custom_field('CreateDate', create_date_str)
    deleted_keys_str = ', '.join(result['DeletedAccessKeys'])  # Convert deleted access keys to a comma-separated string
    pam_user_to_update.custom_field('Access_Key_ID_Removed_during_previous_rotation', deleted_keys_str)
    secrets_manager.save(pam_user_to_update)

最終更新