← All posts

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

AWSGitLabIAMOIDCCI/CD

Тесей із клубком ниток у лабіринті з хмарними іконками

Коли ми використовуємо GitLab для роботи з AWS, нам потрібно надати пайплайнам AWS Identity, яка зможе взаємодіяти з ресурсами у target accounts.

Схема: GitLab підключається до AWS-акаунта 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)

Схема: GitLab runner отримує тимчасові ключі від AWS STS AWS STS


🔹 Варіант 1: GitLab SaaS + OIDC (вже розбирали в попередніх матеріалах)

Мем: «I already did that. Like a month ago»

AWS підтримує OIDC-провайдери, і GitLab.com - серед них. Ми:

  1. Створюємо OIDC provider у AWS з URL https://gitlab.com
  2. Вказуємо 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

Схема CI/CD: GitLab через OIDC деплоїть у S3 і CloudFront у двох AWS-акаунтах Це вже ж було (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.

Що потрібно

  1. 🔐 Root CA (можна свій self-signed або AWS Private CA short-lived, $50/міс)
  2. 🏛️ Trust Anchor в IAM Roles Anywhere (реєструємо rootCA.pem)
  3. 📜 Профіль із дозволеними IAM-ролями (зокрема cross-account)
  4. 🛠️ GitLab Runner
  5. 📦 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.

Схема IAM Roles Anywhere: X.509-сертифікат, CA, trust anchor і STS-токен

✅ Плюси

  • Працює без 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 в інші акаунти

Схема: GitLab Runner в акаунті A асьюмить роль в акаунті B

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. Не загубіться.


Посилання

Мем: Людина-павук показує «окей»


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