AWS Secrets Manager マネージド外部シークレットが GitLab に対応したのでトークン自動ローテーションを試してみた
はじめに
AWS Secrets Manager に「マネージド外部シークレット」という機能があります。外部 SaaS のシークレットを Secrets Manager 側で管理し、Lambda を書かずに自動ローテーションできる仕組みです。
2026年7月6日のアップデートで、対応 SaaS に 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 を対象にマネージド外部シークレットを作成し、自動ローテーション設定と即時ローテーションの動作を確認します。
参考ドキュメント:
- AWS Secrets Manager — Managed external secrets
- AWS Secrets Manager — GitLab access token
- GitLab — Rotate a personal access token (API)
検証内容
事前準備
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 が 25439804 → 25439869 に変わりました。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 を使わないため)- 代わりに
externalSecretRotationRoleArnとexternalSecretRotationMetadataが記録される clientRequestTokenが新バージョンの VersionId と一致する
誰がいつローテーションを実行したか、どのロールが使われたかが CloudTrail に自動記録されるため、監査要件を満たしやすくなっています。
注意事項・Tips
daysToExpiry と実際のトークン有効期限
--external-secret-rotation-metadata で daysToExpiry: 30 を指定しました。ローテーション後の新トークンの expires_at は 2026-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 や手動でローテーションしているケースでは、マネージド外部シークレットへの移行を検討してみてください。








