외부 저장소에 대한 정적 객체
GitLab을 구성하여 아카이브 또는 원시 블롭과 같은 저장소 정적 객체를 외부 저장소(콘텐츠 전송 네트워크(CDN)와 같은)에서 제공하도록 구성합니다.
외부 저장소 구성
정적 객체용 외부 저장소를 구성하려면 다음을 수행합니다:
- 왼쪽 사이드바에서 맨 아래에서 관리자를 선택합니다.
- 설정 > 저장소를 선택합니다.
- 저장소 정적 객체를 위한 외부 저장소를 확장합니다.
- 기본 URL과 임의의 토큰을 입력합니다. 외부 저장소 설정에서 이들 값을
ORIGIN_HOSTNAME
과STORAGE_TOKEN
으로 설정하는 스크립트를 사용합니다. - 변경 사항 저장을 선택합니다.
사용자가 외부 저장소를 우회하여 응용 프로그램에 직접 액세스하지 못하게 하기 위해 토큰이 필요하며, GitLab은 외부 저장소에서 생성된 요청의 X-Gitlab-External-Storage-Token
헤더에 이 토큰이 설정되기를 예상합니다.
개인용 정적 객체 제공
GitLab은 개인 프로젝트에 속하는 정적 객체 URL에 대해 사용자별 토큰을 추가하여 외부 저장소가 사용자를 대신하여 인증할 수 있도록 합니다.
외부 저장소에서 생성된 요청 처리 시, GitLab은 사용자가 요청된 객체에 액세스할 수 있는지 확인하기 위해 다음을 확인합니다:
-
token
쿼리 매개변수. -
X-Gitlab-Static-Object-Token
헤더.
요청 흐름 예시
다음 예시에서는 사용자, GitLab, 콘텐츠 전송 네트워크 간에 발생하는 일련의 요청과 응답을 보여줍니다.
외부 저장소 설정
본 절차는 외부 저장소로 Cloudflare Workers을 사용하지만, 다른 CDN이나 서비스 기능(FaaS) 시스템을 동일한 원칙을 적용하여 작동할 수 있습니다.
- 아직 하지 않은 경우, Cloudflare Worker 도메인을 선택합니다.
-
다음 스크립트에서 첫 두 상수에 대한 다음 값을 설정합니다:
-
ORIGIN_HOSTNAME
: GitLab 설치의 호스트명. -
STORAGE_TOKEN
: 임의의 안전한 토큰. UNIX 기기에서pwgen -cn1 64
명령을 실행하여 토큰을 가져올 수 있습니다. 이 토큰은 정적 객체용 외부 저장소를 구성하는 섹션에 설명된 대로 관리자 영역에 저장합니다.const ORIGIN_HOSTNAME = 'gitlab.installation.com' // FIXME: SET CORRECT VALUE const STORAGE_TOKEN = 'very-secure-token' // FIXME: SET CORRECT VALUE const CACHE_PRIVATE_OBJECTS = false const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 'Access-Control-Allow-Headers': 'X-Csrf-Token, X-Requested-With', } self.addEventListener('fetch', event => event.respondWith(handle(event))) async function handle(event) { try { let response = await verifyAndHandle(event); // responses returned from cache are immutable, so we recreate them // to set CORS headers response = new Response(response.body, response) response.headers.set('Access-Control-Allow-Origin', '*') return response } catch (e) { return new Response('An error occurred!', {status: e.statusCode || 500}) } } async function verifyAndHandle(event) { if (!validRequest(event.request)) { return new Response(null, {status: 400}) } if (event.request.method === 'OPTIONS') { return handleOptions(event.request) } return handleRequest(event) } function handleOptions(request) { // Make sure the necessary headers are present // for this to be a valid pre-flight request if ( request.headers.get('Origin') !== null && request.headers.get('Access-Control-Request-Method') !== null && request.headers.get('Access-Control-Request-Headers') !== null ) { // Handle CORS pre-flight request return new Response(null, { headers: CORS_HEADERS, }) } else { // Handle standard OPTIONS request return new Response(null, { headers: { Allow: 'GET, HEAD, OPTIONS', }, }) } } async function handleRequest(event) { let cache = caches.default let url = new URL(event.request.url) let static_object_token = url.searchParams.get('token') let headers = new Headers(event.request.headers) url.host = ORIGIN_HOSTNAME url = normalizeQuery(url) headers.set('X-Gitlab-External-Storage-Token', STORAGE_TOKEN) if (static_object_token !== null) { headers.set('X-Gitlab-Static-Object-Token', static_object_token) } let request = new Request(url, { headers: headers }) let cached_response = await cache.match(request) let is_conditional_header_set = headers.has('If-None-Match') if (cached_response) { return cached_response } // We don't want to override If-None-Match that is set on the original request if (cached_response && !is_conditional_header_set) { headers.set('If-None-Match', cached_response.headers.get('ETag')) } let response = await fetch(request, { headers: headers, redirect: 'manual' }) if (response.status == 304) { if (is_conditional_header_set) { return response } else { return cached_response } } else if (response.ok) { response = new Response(response.body, response) // cache.put will never cache any response with a Set-Cookie header response.headers.delete('Set-Cookie') if (CACHE_PRIVATE_OBJECTS) { response.headers.delete('Cache-Control') } event.waitUntil(cache.put(request, response.clone())) } return response } function normalizeQuery(url) { let searchParams = url.searchParams url = new URL(url.toString().split('?')[0]) if (url.pathname.includes('/raw/')) { let inline = searchParams.get('inline') if (inline == 'false' || inline == 'true') { url.searchParams.set('inline', inline) } } else if (url.pathname.includes('/-/archive/')) { let append_sha = searchParams.get('append_sha') let path = searchParams.get('path') if (append_sha == 'false' || append_sha == 'true') { url.searchParams.set('append_sha', append_sha) } if (path) { url.searchParams.set('path', path) } } return url } function validRequest(request) { let url = new URL(request.url) let path = url.pathname if (/^(.+)(\/raw\/|\/-\/archive\/)/.test(path)) { return true } return false }
-
- 이 스크립트로 새로운 워커를 만듭니다.
-
ORIGIN_HOSTNAME
및STORAGE_TOKEN
의 값을 복사합니다. 이들 값은 정적 객체용 외부 저장소를 구성하는 데 사용됩니다.