AWS Secrets Manager マネージド外部シークレットが GitLab に対応したのでトークン自動ローテーションを試してみた

AWS Secrets Manager マネージド外部シークレットが GitLab に対応したのでトークン自動ローテーションを試してみた

AWS Secrets Manager のマネージド外部シークレットで GitLab アクセストークンのローテーション設定と即時実行を試しました。Lambda を書かずに、IAM ロールとシークレットの設定でトークンのローテーションが完結します。セットアップ手順と動作確認の結果をまとめます。
2026.07.07

はじめに

AWS Secrets Manager に「マネージド外部シークレット」という機能があります。外部 SaaS のシークレットを Secrets Manager 側で管理し、Lambda を書かずに自動ローテーションできる仕組みです。

https://dev.classmethod.jp/articles/update-aws-secrets-manager-managed-external-secrets/

2026年7月6日のアップデートで、対応 SaaS に Paddle と GitLab が追加されました。

https://aws.amazon.com/about-aws/whats-new/2026/07/aws-secrets-manager-managed-external-secrets-paddle-gitlab/

GitLab と AWS の連携にはいくつかの方式があり、それぞれ方向性が異なります。

方式 方向 用途
GitLab OIDC → AWS IAM Role GitLab → AWS CI/CD から AWS リソースにシークレットレスアクセス
GitLab CI/CD aws_secrets_manager GitLab → AWS CI/CD ジョブ内で Secrets Manager の値を参照
マネージド外部シークレット(今回) AWS → GitLab AWS が GitLab トークンを管理・自動ローテーション

従来方式と比較すると以下の違いがあります。

観点 従来(手動 or カスタム Lambda) マネージド外部シークレット
ローテーション実装 カスタム Lambda 開発・保守 設定のみ(Lambda 不要)
旧トークン失効 自前実装 GitLab rotate API で新旧トークンを一括処理
監査 CloudTrail + 自前ログ AWS 側操作は CloudTrail に記録
IP 制限 NAT IP 手動管理 AWS マネージドプレフィックスリスト
コスト Lambda 実行 + 開発工数 Secrets Manager 料金のみ

※ IP 制限の行は公式ドキュメントの記載に基づくもので、今回の検証では未確認です。

本記事では、GitLab Personal Access Token を対象にマネージド外部シークレットを作成し、自動ローテーション設定と即時ローテーションの動作を確認します。

参考ドキュメント:

検証内容

事前準備

GitLab Personal Access Token の作成

GitLab で Personal Access Token を作成します。スコープは api を指定しました。

  • Token Name: aws-secrets-manager-test
  • Scopes: api
  • Expires: 2026-08-06

作成後、Token ID(今回は 25439804)とトークン値を控えます。

IAM ポリシーの作成

Secrets Manager のローテーション処理に必要な権限を定義します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowRotationAccess",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:DescribeSecret",
        "secretsmanager:GetSecretValue",
        "secretsmanager:PutSecretValue",
        "secretsmanager:UpdateSecretVersionStage"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "secretsmanager:resource/Type": "GitLabAccessToken"
        }
      }
    },
    {
      "Sid": "AllowPasswordGenerationAccess",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetRandomPassword"],
      "Resource": "*"
    }
  ]
}

Condition に "secretsmanager:resource/Type": "GitLabAccessToken" を指定しています。これにより、このポリシーの権限がマネージド外部シークレット(GitLabAccessToken タイプ)にのみ適用されます。通常のシークレットには影響しません。

aws iam create-policy \
  --policy-name SecretsManagerGitLabRotationPolicy \
  --policy-document file://policy.json

IAM ロールの作成

Secrets Manager サービスがこのロールを引き受けてローテーション処理を実行します。

Trust Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SecretsManagerPrincipalAccess",
      "Effect": "Allow",
      "Principal": {"Service": "secretsmanager.amazonaws.com"},
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {"aws:SourceAccount": "123456789012"},
        "ArnLike": {"aws:SourceArn": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:*"}
      }
    }
  ]
}
aws iam create-role \
  --role-name SecretsManagerGitLabRotationRole \
  --assume-role-policy-document file://trust-policy.json

aws iam attach-role-policy \
  --role-name SecretsManagerGitLabRotationRole \
  --policy-arn arn:aws:iam::123456789012:policy/SecretsManagerGitLabRotationPolicy

シークレット作成

--type GitLabAccessToken を指定してシークレットを作成します。

aws secretsmanager create-secret \
  --name test/gitlab/pat-managed \
  --type GitLabAccessToken \
  --secret-string '{"token":"glpat-****","tokenId":"25439804","gitlabUrl":"https://gitlab.com"}' \
  --region ap-northeast-1

--type GitLabAccessToken の指定により、マネージド外部シークレットとして認識されます。

SecretString は以下の形式です。

キー 内容
token GitLab のアクセストークン値
tokenId トークンの数値 ID(GitLab の /personal_access_tokens API で確認可能)
gitlabUrl GitLab インスタンスの URL

ローテーション設定・実行

作成したシークレットに対してローテーションを設定し、即時実行します。

aws secretsmanager rotate-secret \
  --secret-id test/gitlab/pat-managed \
  --external-secret-rotation-role-arn arn:aws:iam::123456789012:role/SecretsManagerGitLabRotationRole \
  --external-secret-rotation-metadata '[{"Key":"daysToExpiry","Value":"30"}]' \
  --rotation-rules '{"AutomaticallyAfterDays":30}' \
  --rotate-immediately \
  --region ap-northeast-1

ポイント:

  • --external-secret-rotation-role-arn: 事前に作成した IAM ロールを指定。通常のローテーションで使う --rotation-lambda-arn は不要です
  • --external-secret-rotation-metadata: 新トークンの有効期限に関するメタデータを指定。今回は daysToExpiry=30 を設定しました
  • --rotation-rules: 自動ローテーションの間隔
  • --rotate-immediately: 設定と同時にローテーションを実行

ローテーション結果確認

バージョンステージの確認

ローテーション完了後、describe-secret でバージョンステージを確認します。

aws secretsmanager describe-secret \
  --secret-id test/gitlab/pat-managed \
  --query 'VersionIdsToStages' \
  --region ap-northeast-1
{
  "08d7dd1e-e299-4aeb-8dc9-693ceb42e6f1": ["AWSCURRENT", "AWSPENDING"],
  "9c1a8d5b-bb3f-4ae3-820b-48a14e501cac": ["AWSPREVIOUS"]
}

新しいバージョン(08d7dd1e-...)が AWSCURRENT に昇格し、元のバージョン(9c1a8d5b-...)が AWSPREVIOUS に移動しています。AWSPENDING と AWSCURRENT が同一バージョンに共存している点については、注意事項セクションで補足します。

Token ID の変化

get-secret-value で新しいトークンの内容を確認します。

aws secretsmanager get-secret-value \
  --secret-id test/gitlab/pat-managed \
  --region ap-northeast-1 \
  --query 'SecretString' --output text | jq .
{
  "token": "glpat-****",
  "tokenId": "25439869",
  "gitlabUrl": "https://gitlab.com"
}

Secrets Manager が保持するトークンの ID が 2543980425439869 に変わりました。GitLab 側での有効性は後述の API 呼び出しで確認します。

新トークンの有効性確認

ローテーション後のトークンで GitLab API を呼び出して有効性を確認します。

curl --header "PRIVATE-TOKEN: glpat-****" \
  "https://gitlab.com/api/v4/personal_access_tokens/self" | jq .
{
  "id": 25439869,
  "name": "aws-secrets-manager-test",
  "active": true,
  "scopes": ["api"],
  "expires_at": "2026-08-06",
  "created_at": "2026-07-07T06:38:11.770Z"
}

新しいトークンは有効(active: true)です。

旧トークンの失効確認

ローテーション前のトークン(Token ID: 25439804)でアクセスすると 401 が返ります。

curl -s -o /dev/null -w "%{http_code}" \
  --header "PRIVATE-TOKEN: glpat-****" \
  "https://gitlab.com/api/v4/personal_access_tokens/self"
401

GitLab の rotate API により、新トークン発行と旧トークン失効が一連の処理として実行されました。これは rotate API の仕様 通りの動作です。

なお、ローテーション開始から Secrets Manager 側で AWSCURRENT 昇格を確認するまで約43秒でした。

ローテーション後のトークンで GitLab API 操作

ローテーション後のトークンが読み取りだけでなく書き込み操作にも使えることを確認します。

プロジェクト情報取得:

curl --header "PRIVATE-TOKEN: glpat-****" \
  "https://gitlab.com/api/v4/projects?owned=true&simple=true" | jq '.[0] | {id, name, web_url}'
{
  "id": 72894561,
  "name": "test-project",
  "web_url": "https://gitlab.com/personal-group/test-project"
}

Issue 作成:

curl --request POST \
  --header "PRIVATE-TOKEN: glpat-****" \
  --header "Content-Type: application/json" \
  --data '{"title":"Test issue from rotated token"}' \
  "https://gitlab.com/api/v4/projects/72894561/issues" | jq '{id, iid, title, state}'
{
  "id": 198765432,
  "iid": 1,
  "title": "Test issue from rotated token",
  "state": "opened"
}

Pipeline trigger 作成:

curl --request POST \
  --header "PRIVATE-TOKEN: glpat-****" \
  --form "description=trigger" \
  "https://gitlab.com/api/v4/projects/72894561/triggers" | jq '{id, description, created_at}'
{
  "id": 5326232,
  "description": "trigger",
  "created_at": "2026-07-07T06:42:15.123Z"
}

いずれも正常に動作しました。

CloudTrail でのイベント確認

CloudTrail で RotateSecret イベントを確認します。

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=RotateSecret \
  --max-results 1 \
  --region ap-northeast-1 \
  --query 'Events[0].CloudTrailEvent' --output text | jq .
CloudTrail イベント全文
{
  "eventVersion": "1.11",
  "eventTime": "2026-07-07T06:37:29Z",
  "eventSource": "secretsmanager.amazonaws.com",
  "eventName": "RotateSecret",
  "awsRegion": "ap-northeast-1",
  "userIdentity": {
    "type": "AssumedRole",
    "arn": "arn:aws:sts::123456789012:assumed-role/your-role-name/your-role-name"
  },
  "requestParameters": {
    "secretId": "test/gitlab/pat-managed",
    "clientRequestToken": "08d7dd1e-e299-4aeb-8dc9-693ceb42e6f1",
    "rotationRules": {"automaticallyAfterDays": 30},
    "externalSecretRotationMetadata": [{"key": "daysToExpiry", "value": "30"}],
    "externalSecretRotationRoleArn": "arn:aws:iam::123456789012:role/SecretsManagerGitLabRotationRole",
    "rotateImmediately": true
  },
  "responseElements": {
    "versionId": "08d7dd1e-e299-4aeb-8dc9-693ceb42e6f1",
    "name": "test/gitlab/pat-managed",
    "arn": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:test/gitlab/pat-managed-Inxj3H"
  }
}

通常の Lambda ローテーションとの違いとして、以下の特徴があります。

  • rotationLambdaARN フィールドが存在しない(Lambda を使わないため)
  • 代わりに externalSecretRotationRoleArnexternalSecretRotationMetadata が記録される
  • clientRequestToken が新バージョンの VersionId と一致する

誰がいつローテーションを実行したか、どのロールが使われたかが CloudTrail に自動記録されるため、監査要件を満たしやすくなっています。

注意事項・Tips

daysToExpiry と実際のトークン有効期限

--external-secret-rotation-metadatadaysToExpiry: 30 を指定しました。ローテーション後の新トークンの expires_at2026-08-06 でした。これは元トークンと同一の日付であり、同時にローテーション日(2026-07-07)から30日後にも一致するため、本検証では daysToExpiry の効果の有無は判別できませんでした。

GitLab の rotate API では expires_at を指定できますが、今回の検証では元トークンの有効期限と daysToExpiry=30 の結果が同じ日付になったため、Secrets Manager がどの値を優先したかは判別できませんでした。daysToExpiry と元トークンの有効期限が異なるケースで検証すると、挙動をより明確に確認できます。

AWSPENDING ステージの残存

ローテーション完了後も AWSPENDING と AWSCURRENT が同一バージョンに共存しました。Lambda ローテーションの一般的なサンプル実装では finishSecret ステップで AWSPENDING が除去されますが、本検証ではこの状態が残存していました。

アプリケーションが VersionStage=AWSCURRENT を明示的に指定して GetSecretValue を呼ぶ場合、動作に影響はありません。

self-rotation と admin-assisted の使い分け

今回は self-rotation 方式(トークン自身の権限でローテーション)で検証しました。adminSecretArn を指定する admin-assisted 方式もあります。

  • self-rotation: 本検証では api スコープのトークンで動作しました。GitLab の rotate API は self_rotate スコープでも実行可能とされています(未検証)。トークン自体が失効した場合に回復手段がない点に注意が必要です
  • admin-assisted: 管理者トークンを別シークレットに格納し、そちらの権限で対象トークンをローテーション。対象トークン自身に依存せずにローテーションできる方式です

運用要件に応じて選択してください。

まとめ

AWS Secrets Manager のマネージド外部シークレットを使い、GitLab Personal Access Token の自動ローテーション設定と即時ローテーションを確認しました。Lambda 関数の開発・保守は不要で、IAM ロール、シークレット、ローテーション設定によりローテーションが動作します。GitLab 側では rotate API により旧トークンが失効し、新トークンが発行されました。Secrets Manager 側では、ローテーション開始から AWSCURRENT 昇格を確認するまで約43秒でした。

料金は Secrets Manager の通常料金で、月額$0.40のシークレット基本料金と API コール料金が発生します(公式料金ページ、本記事執筆時点)。常時シークレットを参照しない用途であれば、コストパフォーマンスの良い選択肢です。

GitLab 以外にも BigID、Confluent Cloud、Datadog、MongoDB Atlas、Paddle、Salesforce、Snowflake に対応しています。これらの SaaS トークンを Lambda や手動でローテーションしているケースでは、マネージド外部シークレットへの移行を検討してみてください。

参考リンク

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事