인증 개발 가이드라인
GitLab v19.4요약
Authentication 팀은 System Access와 User Profile 두 가지 기능 카테고리를 담당합니다. GitLab의 인증 및 인가에 관한 심층적인 보안 지식은 아래 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
Authentication 팀은 System Access와 User Profile 두 가지 기능 카테고리를 담당합니다. 이 가이드는 두 카테고리의 주요 주제를 참조 위치와 주의할 점과 함께 다룹니다.
GitLab의 인증 및 인가에 관한 심층적인 보안 지식은 아래 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
요청 흐름 개요#
모든 인증 흐름은 current_user로 수렴됩니다.
HTTP Request
│
▼
Rack Middleware (Labkit → Rack::Attack → Warden → CSRF)
│
├── Web Request ──► SessionsController / OmniauthCallbacks ──► Warden Strategies
│
└── API Request ──► API::APIGuard ──► AuthFinders (token lookup)
│
▼
current_user
API 요청에서 AuthFinders(lib/gitlab/auth/auth_finders.rb)는
다음 순서로 인증 소스를 확인합니다.
- 배포 토큰
- Bearer 토큰(job 토큰, PAT 또는 OAuth)
- Job 토큰(헤더 또는 파라미터)
- 세션 폴백(웹에서 시작된 API 호출)
가드레일:
AuthFinders의 토큰 확인 순서를 변경하지 않습니다. 배포 토큰, bearer 토큰, job 토큰, 세션은 정해진 우선순위에 따라 확인됩니다. 순서를 바꾸면 한 토큰 유형이 다른 토큰 유형을 가릴 수 있습니다.
토큰 접두사#
토큰 접두사를 사용하여 디버깅과 코드 리뷰 중에 토큰 유형을 식별합니다.
| 접두사 | 토큰 유형 | 저장 방식 |
|---|---|---|
glpat- |
개인, 프로젝트 또는 그룹 액세스 토큰 | SHA-256 digest |
gldt- |
배포 토큰 | AES-256-GCM 암호화 |
glcbt- |
CI job 토큰 | AES-256-GCM 암호화 |
gloas- |
OAuth 애플리케이션 시크릿 | SHA-512 digest |
토큰 유형과 저장 전략의 전체 목록은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
로그인 및 세션#
웹 로그인은 Devise와 Warden을 사용합니다. 설정은
config/initializers/8_devise.rb에 있습니다(8_ 접두사가 로드 순서를 제어합니다).
| 참조 위치 | 목적 |
|---|---|
app/controllers/sessions_controller.rb |
웹 로그인 및 로그아웃. |
config/initializers/warden.rb |
Warden 콜백, 세션 설정. |
app/models/active_session.rb |
Redis 에서의 세션 추적. |
app/controllers/admin/sessions_controller.rb |
Admin 모드 인증. |
가드레일:
- 병렬 세션 저장소를 따로 만들지 않습니다. Devise와
ActiveSession을 함께 사용합니다. - 로그인 제한에는 Rack::Attack(
config/initializers/rack_attack.rb)을 사용합니다. - 기능 수준의 속도 제한(예: 토큰 생성, 비밀번호 재설정)에는
Gitlab::ApplicationRateLimiter를 사용합니다. 자세한 내용은 application rate limiter를 참고합니다. - 자격 증명(비밀번호, 토큰, 2FA 코드, 복구 코드)을 검증하는 모든 엔드포인트에는 속도 제한을 적용해야 합니다. 속도 제한 키에는 가능한 경우 소스 IP 만이 아니라 자격 증명 주체(로그인 또는 사용자)를 포함해야 합니다. 성공 시 초기화되는 IP 단위 전용 스로틀은 자격 증명 스터핑으로 우회할 수 있습니다.
- 권한이 변경되는 모든 시점(로그인, 2FA 통과, 비밀번호 변경, admin 모드 진입)에 세션 ID를 다시 생성합니다. 세션 고정 공격을 방어합니다.
세션 기반 인증에 대한 자세한 내용은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
액세스 토큰#
개인 액세스 토큰(PAT), 그룹 액세스 토큰, 프로젝트 액세스 토큰,
서비스 계정 PAT는 모두 personal_access_tokens 테이블에 저장되며
동일한 인증 흐름을 공유합니다. 다만 생성 방식, 토큰을 소유하는
사용자 유형, 인증을 마친 뒤에 적용되는 인가 검사는
서로 다릅니다.
개인 액세스 토큰#
| 참조 위치 | 목적 |
|---|---|
app/models/personal_access_token.rb |
PAT 모델, 스코프, 만료 유효성 검사. |
lib/api/personal_access_tokens.rb |
PAT REST API(목록 조회, 취소, 교체). |
app/controllers/user_settings/personal_access_tokens_controller.rb |
PAT UI. |
app/services/personal_access_tokens/ |
생성, 취소, 교체 서비스. |
그룹 및 프로젝트 액세스 토큰#
| 참조 위치 | 목적 |
|---|---|
lib/api/resource_access_tokens.rb |
그룹 및 프로젝트 토큰용 REST API. |
app/services/resource_access_tokens/create_service.rb |
bot 사용자 프로비저닝을 포함한 토큰 생성. |
app/controllers/groups/settings/access_tokens_controller.rb |
그룹 토큰 UI. |
app/controllers/projects/settings/access_tokens_controller.rb |
프로젝트 토큰 UI. |
서비스 계정#
서비스 계정(user_type: :service_account)은 외부 통합과 자동화를 위한
전용 사용자입니다. 인스턴스, 그룹 또는 프로젝트 수준에서
프로비저닝할 수 있습니다.
- 인스턴스 관리자만 인스턴스 수준 서비스 계정을 만들 수 있습니다
(
app/services/users/service_accounts/create_service.rb). - 그룹 Owner만 그룹 수준 서비스 계정을 만들 수 있습니다
(
app/services/namespaces/service_accounts/group_create_service.rb). - Maintainer 이상 권한을 가진 사용자는 프로젝트 수준 서비스 계정을 만들 수 있습니다
(
app/services/namespaces/service_accounts/project_create_service.rb). composite_identity_enforced: true인 서비스 계정은 시트 계산에서 제외됩니다.
| 참조 위치 | 목적 |
|---|---|
lib/api/service_accounts.rb |
인스턴스 SA REST API. |
lib/api/group_service_accounts.rb |
그룹 SA REST API. |
lib/api/project_service_accounts.rb |
프로젝트 SA REST API. |
액세스 토큰 가드레일#
- 임시 토큰 칼럼이나 수동 해싱을 만들지 않습니다.
add_authentication_token_field와 함께TokenAuthenticatableconcern을 사용합니다. 자세한 내용은TokenAuthenticatableconcern 사용을 참고합니다. insecure: true저장 전략으로 새 토큰 유형을 추가하지 않습니다. SHA-256 digest로 저장할 때는digest: true를, 저장 데이터를 AES-256-GCM으로 암호화할 때는encrypted: :required를 사용합니다. 자세한 내용은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.MAX_PERSONAL_ACCESS_TOKEN_LIFETIME_IN_DAYS(365)는 날짜를 지정하지 않았을 때 적용되는 기본 만료 값입니다.require_personal_access_token_expiry가 비활성화된 경우에는 상한으로 강제되지 않습니다.- 교체 중에 토큰 만료 로직을 변경하지 않습니다. 토큰 교체는 원래의 만료 정책을 유지해야 합니다. 과거 사례로 PAT를 교체하면서 서비스 계정의 만료 날짜와 그룹 멤버십 만료가 함께 변경된 일이 있습니다.
- 사용자를 차단할 때는 모든 서브시스템에서 활성 토큰 전체가 무효화되는지 확인합니다. 과거 사례로 차단된 사용자의 PAT가 유효한 컨테이너 레지스트리 JWT를 계속 발급한 일이 있습니다.
- 새 토큰 유형에는 만료를 기본값으로 둡니다. 명시적으로 필요한 경우가 아니라면 만료 없는 토큰 생성을 허용하지 않습니다.
- 새 엔드포인트에서 URL 쿼리 파라미터로 토큰을 받지 않습니다. PAT에는
PRIVATE-TOKEN헤더를, OAuth에는Authorization: Bearer를 사용합니다.
액세스 토큰 인증에 대한 자세한 내용은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
CI job 토큰#
CI job 토큰은 파이프라인 job을 GitLab API에 인증합니다.
| 참조 위치 | 목적 |
|---|---|
lib/ci/job_token/jwt.rb |
JWT 생성 및 서명. |
lib/gitlab/auth/auth_finders.rb |
Job 토큰 추출 및 조회. |
app/models/ci/build.rb |
Build 모델, 토큰 저장(암호화). |
ee/app/models/concerns/ee/ci/job_token/jwt.rb |
EE 확장(composite identity). |
가드레일:
- CI job 토큰은 RSA로 서명한 JWT 입니다. 평문으로 저장하거나 문자열 동등 비교로 비교하지 않습니다.
- Job 토큰은 특정 파이프라인 job으로 스코프가 한정되며 job과 함께 만료됩니다. job 실행 기간을 넘겨 수명을 연장하지 않습니다.
자세한 내용은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
OAuth 및 OpenID Connect#
GitLab은 OAuth 공급자와 클라이언트 역할을 모두 수행합니다. 또한
doorkeeper-openid_connect를 통해 OpenID Connect(OIDC) 공급자로도 동작합니다.
OAuth 및 OIDC 공급자로서의 GitLab#
외부 애플리케이션은 Doorkeeper를 사용하여 GitLab을 통해 사용자를 인증합니다. OIDC는 OAuth 위에 ID 계층을 추가하여 액세스 토큰과 함께 ID 토큰을 발급합니다.
| 참조 위치 | 목적 |
|---|---|
app/controllers/oauth/authorizations_controller.rb |
인가 프롬프트 및 코드 생성. |
app/controllers/oauth/tokens_controller.rb |
토큰 교환 엔드포인트. |
app/models/authn/oauth_application.rb |
OAuth 애플리케이션 등록. |
app/models/oauth_access_token.rb |
액세스 토큰 저장(Sha512Hash). |
config/initializers/doorkeeper.rb |
Doorkeeper 설정, 스코프, 그랜트 흐름. |
config/initializers/doorkeeper_openid_connect.rb |
OIDC 공급자 설정, 클레임, 서명 키. |
app/controllers/oauth/discovery_controller.rb |
OIDC 디스커버리 엔드포인트(/.well-known/openid-configuration). |
OIDC 클레임은 doorkeeper_openid_connect.rb에 정의합니다. 표준 클레임에는
sub, name, email, groups와 권한 기반 그룹 클레임
(https://gitlab.org/claims/groups/owner 등)이 포함됩니다.
GitLab의 OAuth에 대한 자세한 내용은 Authentication & Authorization Security Knowledge Base (내부 문서)를 참고합니다.
가드레일:
- 데이터 노출을 검토하지 않은 채 새 OIDC 클레임을 추가하지 않습니다. 각 클레임은 요청한 애플리케이션이 볼 수 있는 ID 토큰에 포함됩니다.
- OIDC 서명 키(
openid_connect_signing_key)는 Rails 자격 증명에 저장됩니다. 인프라 팀과 협의하지 않고 로깅하거나 노출하거나 교체하지 않습니다. - 토큰은 항상 만료를 지정해 생성합니다. 만료 기간은 가능한 한 짧게 유지합니다.
OAuth 클라이언트로서의 GitLab#
사용자는 OmniAuth 전략을 통해 외부 공급자(Google, GitHub, Azure)로 GitLab에 로그인합니다.
| 참조 위치 | 목적 |
|---|---|
app/controllers/omniauth_callbacks_controller.rb |
모든 공급자의 콜백 핸들러. |
lib/gitlab/auth/o_auth/user.rb |
사용자 조회, 생성, ID 연결. |
lib/gitlab/auth/o_auth/auth_hash.rb |
공급자로부터 받은 인증 데이터 파싱. |
config/initializers/omniauth.rb |
OmniAuth 초기화. |
가드레일:
- 커스텀 OAuth 흐름을 구현하지 않습니다. 외부 공급자에는 OmniAuth 전략을, 공급자 측에는 Doorkeeper를 사용합니다.
- GitLab 이 OAuth 클라이언트로 동작할 때는 PKCE를 사용합니다.
SAML#
SAML은 ID 공급자(Okta, Azure AD)를 통해 SSO를 제공합니다. GitLab은 두 가지 SAML 모드를 지원합니다.
| 측면 | 인스턴스 SAML | Group SAML (EE) |
|---|---|---|
| 범위 | 전체 GitLab 인스턴스 | 최상위 그룹별 |
| 설정 | gitlab.rb 또는 omniauth_providers |
Group Settings UI |
| 라우트 | /users/auth/saml |
/groups/:path/-/saml |
| 전략 | omniauth-saml gem |
커스텀 GroupSaml 전략 |
| 적용 | 인스턴스 전체 | 그룹별 SSO 적용 |
| 참조 위치 | 목적 |
|---|---|
lib/gitlab/auth/saml/ |
인스턴스 수준 SAML 전략 및 ID 연결자. |
ee/lib/gitlab/auth/group_saml/ |
Group SAML: 사용자 확인, ID 연결, 멤버십 업데이트. |
ee/app/controllers/groups/omniauth_callbacks_controller.rb |
Group SAML 콜백 핸들러. |
ee/app/controllers/groups/sso_controller.rb |
Group SSO 페이지. |
ee/app/controllers/groups/saml_providers_controller.rb |
SAML 공급자 설정 UI. |
ee/lib/api/provider_identity.rb |
SAML/SCIM ID 및 extern_uid 관리 API. |
가드레일:
- SAML
RelayState파라미터를 리다이렉트 타깃으로 사용하기 전에 검증합니다. 과거 사례로 SAML Single Logout에서 검증되지 않은 RelayState를 통한 오픈 리다이렉트가 있었습니다. - 모든 SAML 응답에서 XML 서명을 검증하고, 서명된 요소가 신뢰하려는 어설션인지 확인합니다. XML 서명 래핑 공격을 방어합니다. 과거 사례는 다음과 같습니다. #486565 - 인증되지 않은 SAML 로그인 우회.
SCIM#
SCIM은 사용자 수명 주기 관리(프로비저닝 및 디프로비저닝)를 자동화합니다. SCIM은 프로비저닝 전용이며 인증은 처리하지 않습니다.
| 참조 위치 | 목적 |
|---|---|
ee/app/models/scim_identity.rb |
SCIM ID 레코드. |
ee/lib/api/scim/group_scim.rb |
Group SCIM API(RFC 7644). |
ee/app/controllers/groups/settings/domain_verification_controller.rb |
SCIM 용 도메인 검증. |
자세한 내용은 SCIM 설정을 참고합니다.
LDAP#
LDAP은 디렉터리 서비스(Active Directory, OpenLDAP)를 통해 사용자를 인증합니다.
| 참조 위치 | 목적 |
|---|---|
lib/gitlab/auth/ldap/ |
LDAP 인증 및 어댑터. |
ee/lib/gitlab/auth/ldap/ |
EE LDAP 동기화, 그룹 링크. |
ee/app/controllers/groups/ldaps_controller.rb |
LDAP 그룹 동기화 UI. |
ee/lib/api/ldap.rb |
LDAP REST API. |
자세한 내용은 GitLab과 LDAP 통합을 참고합니다.
2단계 인증#
2단계 인증(2FA)은 로그인에 두 번째 검증 단계를 추가합니다.
| 참조 위치 | 목적 |
|---|---|
app/controllers/profiles/two_factor_auths_controller.rb |
2FA 설정 UI(TOTP, WebAuthn). |
app/controllers/profiles/passkeys_controller.rb |
패스키 관리. |
app/controllers/concerns/authenticates_with_two_factor.rb |
로그인 흐름에서의 2FA 적용. |
app/controllers/concerns/enforces_two_factor_authentication.rb |
2FA 요구 사항 확인. |
가드레일:
- 인증 전에 로그인 파라미터(사용자 이름, 이메일)를 정제합니다. 과거 사례로 로그인 파라미터에 포함된 공백 문자가 WebAuthn 2FA를 우회한 일이 있습니다(#585333).
- 패스키나 2FA 장치를 삭제하면 해당 자격 증명으로 인증된 모든 세션을 무효화합니다.
read_api등 스코프가 제한된 토큰이 sudo 모드나 상승된 인증 요구 사항을 우회하도록 허용하지 않습니다.
SSH 키#
SSH 키는 SSH를 통한 Git 작업을 인증합니다.
| 참조 위치 | 목적 |
|---|---|
app/controllers/user_settings/ssh_keys_controller.rb |
SSH 키 관리 UI. |
lib/api/keys.rb |
SSH 키 REST API. |
app/models/key.rb |
SSH 키 모델 및 유효성 검사. |
ID 연결 및 extern_uid#
사용자가 외부 공급자를 통해 인증하면 GitLab은 GitLab 사용자와 공급자
계정을 연결하는 Identity 레코드를 생성합니다.
Identity 모델(app/models/identity.rb)은 다음을 저장합니다.
user_id: GitLab 사용자입니다.provider: 공급자 이름입니다(예:google_oauth2,group_saml,ldapmain).extern_uid: 공급자가 부여한 사용자의 고유 식별자입니다.saml_provider_id: Group SAML에서 그룹의 SAML 설정에 연결됩니다.
로그인 과정에서 GitLab은 ID를 통해 사용자를 확인합니다.
- 외부 공급자가 UID를 포함한 인증 응답을 반환합니다.
- GitLab은 공급자와 UID가 일치하는
Identity레코드를 조회합니다. - 레코드를 찾고 신뢰할 수 있으면 GitLab은 연결된 사용자로 로그인합니다.
- 찾지 못하면 GitLab은 새 사용자를 만들거나 계정 연결을 요청합니다.
가드레일:
- 공급자가 해당 UID가 그 계정에 속한다는 점을 보장하지 않는 경우
extern_uid만으로 사용자를 확인하지 않습니다.
비밀번호 관리#
| 참조 위치 | 목적 |
|---|---|
app/controllers/passwords_controller.rb |
비밀번호 재설정 흐름. |
app/controllers/user_settings/passwords_controller.rb |
비밀번호 변경. |
app/models/concerns/recoverable_by_any_email.rb |
기본 또는 보조 이메일을 통한 재설정. |
가드레일:
- 비밀번호 재설정 흐름에서 사용자가 제공한 모든 리다이렉트 타깃은
InternalRedirect#sanitize_redirect또는safe_redirect_path로 검증합니다. 원시 파라미터를redirect_to에 전달하지 않습니다. 항상 폴백을 지정합니다.
redirect_to sanitize_redirect(params[:redirect]) || root_path
- 비밀번호 재설정 토큰은 한 번만 사용할 수 있고 수명이 짧아야 하며 사용에 성공하면 무효화되어야 합니다. 재설정에 성공하면 해당 사용자의 다른 모든 활성 세션을 무효화해야 합니다.
- 비밀번호 재설정 엔드포인트는 이메일 파라미터가 단일 문자열 값인지 검증하고, 배열이나 그 밖의 비문자열 유형으로 전달된 요청은 모두 거부해야 합니다. 과거 사례는 다음과 같습니다. #436084 - 비밀번호 재설정을 통한 계정 탈취.
- 로그인 실패 응답과 비밀번호 재설정 응답은 "사용자는 있으나 자격 증명이 틀림" 과 "사용자가 없음" 사이에 차이가 없어야 합니다. 동일한 응답 본문과 동일한 타이밍을 사용합니다. 계정 열거 공격을 방어합니다.
사용자 프로필#
| 참조 위치 | 목적 |
|---|---|
app/controllers/profiles_controller.rb |
프로필 표시. |
app/controllers/user_settings/profiles_controller.rb |
프로필 편집. |
app/controllers/profiles/emails_controller.rb |
이메일 관리. |
app/controllers/profiles/avatars_controller.rb |
아바타 업로드. |
app/controllers/profiles/preferences_controller.rb |
사용자 환경 설정. |
API 인증#
API 요청은 API::APIGuard(lib/api/api_guard.rb)를 통해 인증합니다.
API::APIGuard는 OAuth2 bearer 토큰, 스코프 적용, DPoP 검사, 오류
처리를 담당합니다.
| 참조 위치 | 목적 |
|---|---|
lib/api/api_guard.rb |
API 인증 모듈. |
lib/gitlab/auth/auth_finders.rb |
API 요청의 토큰 조회. |
lib/gitlab/auth/request_authenticator.rb |
Rack::Attack과 로깅 전용 요청 ID 확인. 액세스 제어에는 사용하지 않습니다. |
app/services/auth/dpop_authentication_service.rb |
DPoP proof-of-possession 검증. |
가드레일:
- 커스텀 API 인증을 구현하지 않습니다.
API::APIGuard를 사용합니다. - JWT 나 토큰 파싱을 직접 구현하지 않습니다.
Authn::IamService::JwtValidationService를 사용합니다. - 토큰 생성 엔드포인트와 검증 엔드포인트의 속도 제한에는
check_rate_limit!를 사용합니다.
공유 인프라#
인증 코드는 웹, API, OAuth, SAML 진입점 전체에서 공유합니다. 공유 인프라를 변경하면 모든 흐름이 중단될 수 있습니다.
| 참조 위치 | 목적 |
|---|---|
app/models/authn/, app/services/authn/, lib/authn/ |
인증 모델, 서비스, 토큰 프레임워크. |
lib/gitlab/auth/ |
핵심 인증 인프라. |
app/controllers/concerns/internal_redirect.rb |
안전한 리다이렉트 검증. |
가드레일:
- 모든 호출부를 확인하지 않은 채 인증 메서드의 반환 유형이나 nil-vs-value 시맨틱을 변경하지 않습니다.
- 인증 코드는 모든 진입점에서 공유합니다. 공유 인프라를 수정할 때는 모든 흐름이 여전히 동작하는지 확인합니다.
- 토큰 값, 비밀번호 값, 세션 ID는 절대 로깅하지 않습니다. 오류 메시지, 감사 이벤트, 구조화된 로그, 백트레이스 모두 해당됩니다.
- 토큰 동등 비교에는 일정 시간 함수
(
ActiveSupport::SecurityUtils.secure_compare)를 사용해야 합니다. 토큰 검증에 대한 타이밍 공격을 방어합니다. - 사용자 차단, 금지, 비활성화를 수정할 때는 차단 상태를 로그인 시점만이 아니라 사용 시점에도 확인하는지 검증합니다. 토큰, 세션, API 액세스는 모두 차단 상태를 따라야 합니다.
- 새 인증 경로나 토큰 유형을 추가할 때는
user.blocked?검사를 따르는지 확인합니다. - 사용자가 제공한 원시 파라미터를
redirect_to에 전달하지 않습니다.InternalRedirect#sanitize_redirect또는safe_redirect_path를 사용합니다. 리다이렉트 헬퍼는 유효하지 않은 타깃에nil을 반환합니다. 항상 폴백을 지정합니다.
기능 플래그#
인증 관련 기능 플래그에는 group: group::authentication을 사용합니다.