GitLab, AWS та ізольовані середовища: три шляхи до AssumeRole

Коли ми використовуємо GitLab для роботи з AWS, нам потрібно надати пайплайнам AWS Identity, яка зможе взаємодіяти з ресурсами у target accounts.
GitLab -> AWS problem
❌ Чому не підходять IAM Users?
- Потрібно зберігати access key + secret у GitLab CI
- Потрібно вручну або скриптами/костилями робити їх rotation
- Це найгірший і найменш безпечний варіант (і взагалі за це вже дають по руках)
🧟 IAM User = Мінотавр у лабіринті безвиході
✅ Ідеальний варіант - AssumeRole
- GitLab Runner асьюмить IAM Role у dev/test/prod акаунтах
- Але щоб AssumeRole+STS працювало, aws cli у GitLab runner має мати вже якусь AWS identity (IAM Role)
AWS STS
🔹 Варіант 1: GitLab SaaS + OIDC (вже розбирали в попередніх матеріалах)

AWS підтримує OIDC-провайдери, і GitLab.com - серед них. Ми:
- Створюємо OIDC provider у AWS з URL https://gitlab.com
- Вказуємо audience, наприклад
https://gitlab.com(значенняaudу токені GitLab за замовчуванням)
📦 AWS робить:
curl https://gitlab.com/.well-known/openid-configuration
{"issuer":"https://gitlab.com","authorization_endpoint":"https://gitlab.com/oauth/authorize","token_endpoint":"https://gitlab.com/oauth/token","revocation_endpoint":"https://gitlab.com/oauth/revoke","introspection_endpoint":"https://gitlab.com/oauth/introspect","userinfo_endpoint":"https://gitlab.com/oauth/userinfo","jwks_uri":"https://gitlab.com/oauth/discovery/keys","scopes_supported":["api","read_api","read_user","create_runner","manage_runner","k8s_proxy","self_rotate","read_repository","write_repository","read_registry","write_registry","read_virtual_registry","write_virtual_registry","read_observability","write_observability","ai_features","sudo","admin_mode","read_service_ping","openid","profile","email","ai_workflows","user:*"],"response_types_supported":["code"],"response_modes_supported":["query","fragment","form_post"],"grant_types_supported":["authorization_code","password","client_credentials","device_code","refresh_token"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"claim_types_supported":["normal"],"claims_supported":["iss","sub","aud","exp","iat","sub_legacy","name","nickname","preferred_username","email","email_verified","website","profile","picture","groups","groups_direct","https://gitlab.org/claims/groups/owner","https://gitlab.org/claims/groups/maintainer","https://gitlab.org/claims/groups/developer"],"code_challenge_methods_supported":["plain","S256"]}%
curl https://gitlab.com/oauth/discovery/keys
{"keys":[{"kty":"RSA","kid":"kewiQq9jiC84CvSsJYOB-N6A8WFLSV20Mb-y7IlWDSQ","e":"AQAB","n":"5RyvCSgBoOGNE03CMcJ9Bzo1JDvsU8XgddvRuJtdJAIq5zJ8fiUEGCnMfAZI4of36YXBuBalIycqkgxrRkSOENRUCWN45bf8xsQCcQ8zZxozu0St4w5S-aC7N7UTTarPZTp4BZH8ttUm-VnK4aEdMx9L3Izo0hxaJ135undTuA6gQpK-0nVsm6tRVq4akDe3OhC-7b2h6z7GWJX1SD4sAD3iaq4LZa8y1mvBBz6AIM9co8R-vU1_CduxKQc3KxCnqKALbEKXm0mTGsXha9aNv3pLNRNs_J-cCjBpb1EXAe_7qOURTiIHdv8_sdjcFTJ0OTeLWywuSf7mD0Wpx2LKcD6ImENbyq5IBuR1e2ghnh5Y9H33cuQ0FRni8ikq5W3xP3HSMfwlayhIAJN_WnmbhENRU-m2_hDPiD9JYF2CrQneLkE3kcazSdtarPbg9ZDiydHbKWCV-X7HxxIKEr9N7P1V5HKatF4ZUrG60e3eBnRyccPwmT66i9NYyrcy1_ZNN8D1DY8xh9kflUDy4dSYu4R7AEWxNJWQQov525v0MjD5FNAS03rpk4SuW3Mt7IP73m-_BpmIhW3LZsnmfd8xHRjf0M9veyJD0--ETGmh8t3_CXh3I3R9IbcSEntUl_2lCvc_6B-m8W-t2nZr4wvOq9-iaTQXAn1Au6EaOYWvDRE","use":"sig","alg":"RS256"},{"kty":"RSA","kid":"4i3sFE7sxqNPOT7FdvcGA1ZVGGI_r-tsDXnEuYT4ZqE","e":"AQAB","n":"4cxDjTcJRJFID6UCgepPV45T1XDz_cLXSPgMur00WXB4jJrR9bfnZDx6dWqwps2dCw-lD3Fccj2oItwdRQ99In61l48MgiJaITf5JK2c63halNYiNo22_cyBG__nCkDZTZwEfGdfPRXSOWMg1E0pgGc1PoqwOdHZrQVqTcP3vWJt8bDQSOuoZBHSwVzDSjHPY6LmJMEO42H27t3ZkcYtS5crU8j2Yf-UH5U6rrSEyMdrCpc9IXe9WCmWjz5yOQa0r3U7M5OPEKD1-8wuP6_dPw0DyNO_Ei7UerVtsx5XSTd-Z5ujeB3PFVeAdtGxJ23oRNCq2MCOZBa58EGeRDLR7Q","use":"sig","alg":"RS256"},{"kty":"RSA","kid":"UEtnUohTq58JiJzxHhBLSU0yTpsmW-9EY1Wykha6VIg","e":"AQAB","n":"9UAG0U59NZ3MBMQkjCVuA8c0ZHEL8SEljnXYYxAuuvy4P79XxTYNodmBAioe1CBsdOmFjjdtXzPIxYv_zEHwkI5WoL1U0r83Q8RSbcl_YSCjfq32TW1hj1KQe0bjzx1TohtnOZSIq-0_8QLbdJrwN7LnBHkalAdMYFk9qUEFlTP-jwUIxztmjpok_-d6W1621iDQwUzYqKiTYc7ZQdC3Bf5jv-8yTm7pMQrR0W6XvPNnRwJmGZdkDH1ZC6okTzLsaMBgMV5awtXJeSZqrR3Qy3ATX6hiitmld9K3FyKFyyIOpaygjltKVzNy5giwnDfTHaGs24Y51Jy1SZ51vqfEFQ","use":"sig","alg":"RS256"}]}%
Звідти:
- отримує issuer, jwks_uri, token_endpoint
- завантажує JWKS для перевірки підписів JWT
- бере thumbprint SSL-сертифіката
🧾 Коли GitLab запускає пайплайн:
- створюється CI_JOB_JWT або id_tokens;
- AWS перевіряє підпис і асьюмить роль:
assume-role-example:
image:
name: amazon/aws-cli:latest
entrypoint: [""]
id_tokens:
MY_OIDC_TOKEN:
aud: https://service-url.example.com
before_script:
- mkdir -p ~/.aws
- echo "${MY_OIDC_TOKEN}" > /tmp/web_identity_token
- echo -e "[profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token" > ~/.aws/config
script:
- aws sts get-caller-identity
Це вже ж було (c)
🚧 Але що, якщо GitLab - self-hosted?
❗ Якщо GitLab CE працює у внутрішньому середовищі без публічного доступу (gitlab.test.local) - AWS не зможе підтягнути
.well-known/openid-configuration, і довіряти GitLab як OIDC-провайдеру просто не буде.
🔹 Варіант 2: GitLab CE (приватний) + IAM Roles Anywhere
Цей сервіс дозволяє використовувати X.509-сертифікати для того, щоб асьюмити IAM-ролі. Працює з будь-якого місця, навіть із bare-metal або on-prem EC2.
Що потрібно
- 🔐 Root CA (можна свій self-signed або AWS Private CA short-lived, $50/міс)
- 🏛️ Trust Anchor в IAM Roles Anywhere (реєструємо rootCA.pem)
- 📜 Профіль із дозволеними IAM-ролями (зокрема cross-account)
- 🛠️ GitLab Runner
- 📦 aws_signing_helper
aws_signing_helper credential-process \
--certificate runner.crt \
--private-key runner.key \
--trust-anchor-arn arn:... \
--profile-arn arn:... \
--role-arn arn:aws:iam::<target-account>:role/DeployRole
➡️ AWS перевіряє підпис, CA і повертає тимчасові credentials.

✅ Плюси
- Працює без GitLab SaaS
- Можна використовувати власний CA (безкоштовно)
- Підтримує cross-account AssumeRole
⚠️ Мінуси
- Short-lived сертифікати, які живуть до 7 днів, або власний CA
- Потрібно додавати сертифікати на ранери (завантажувати в before_script) або додавати їх під час збирання GitLab runner docker image
- Потрібно мати aws_signing_helper cli на gitlab runner
🔹 Варіант 3: GitLab Runner в AWS
Це - найпростіший варіант, якщо Runner запускається на EC2:
- Додаємо IAM Instance Profile до EC2
- Роль має дозвіл на sts:AssumeRole в інші акаунти

aws sts assume-role --role-arn arn:aws:iam::<target-account>:role/DeployRole \
--role-session-name "gitlab-ci"
- ✔️ Не потрібні JWT / OIDC
- ✔️ Не потрібні сертифікати
- ✔️ Усе відбувається через EC2 metadata
Як Тесей у лабіринті - GitLab Runner шукає шлях до AWS-ресурсів. Наша нитка Аріадни - правильний вибір identity. Не загубіться.
Посилання
- https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html
- https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies-cross-account-resource-access.html
- https://aws.amazon.com/blogs/security/iam-roles-anywhere-with-an-external-certificate-authority/

Вперше опубліковано на LinkedIn 4 квітня 2025 р.: оригінал.