静态对象的外部存储

  • Tier: 基础版, 专业版, 旗舰版
  • Offering: 私有化部署

配置极狐GitLab 来从外部存储(如内容分发网络 (CDN))提供存储库静态对象(如档案或原始 blob)。

配置外部存储#

要为静态对象配置外部存储:

  1. 在左侧边栏的底部,选择 管理员
  2. 选择 设置 > 存储库
  3. 展开 存储库静态对象的外部存储
  4. 输入基本 URL 和一个任意的令牌。当你设置外部存储时,使用一个脚本将这些值设置为 ORIGIN_HOSTNAMESTORAGE_TOKEN
  5. 选择 保存更改

令牌是区分来自外部存储的请求所必需的,以防止用户绕过外部存储直接访问应用程序。极狐GitLab 期望在来自外部存储的请求中,在 X-Gitlab-External-Storage-Token 标头中设置此令牌。

提供私有静态对象#

极狐GitLab 为属于私有项目的静态对象 URL 添加用户特定的令牌,以便可以代表用户进行外部存储身份验证。

在处理来自外部存储的请求时,极狐GitLab 检查以下内容以确认用户可以访问请求的对象:

  • token 查询参数。
  • X-Gitlab-Static-Object-Token 标头。

请求流程示例#

以下示例显示了用户、极狐GitLab 和内容分发网络之间的一系列请求和响应:

Rendering chart...

设置外部存储#

虽然此过程使用 Cloudflare Workers 进行外部存储,但其他 CDN 或函数即服务 (FaaS) 系统应该可以使用相同的原则。

  1. 如果尚未选择 Cloudflare Worker 域,则选择一个。

  2. 在以下脚本中,为前两个常量设置以下值:

    • ORIGIN_HOSTNAME: 你的极狐GitLab 安装的主机名。

    • STORAGE_TOKEN: 任何任意的安全令牌。你可以通过在 UNIX 机器上运行 pwgen -cn1 64 来获取令牌。将此令牌保存到 管理员 区域,如配置部分所述。

      javascript
      1const ORIGIN_HOSTNAME = 'gitlab.installation.com' // FIXME: SET CORRECT VALUE 2const STORAGE_TOKEN = 'very-secure-token' // FIXME: SET CORRECT VALUE 3const CACHE_PRIVATE_OBJECTS = false 4 5const CORS_HEADERS = { 6 'Access-Control-Allow-Origin': '*', 7 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', 8 'Access-Control-Allow-Headers': 'X-Csrf-Token, X-Requested-With', 9} 10 11self.addEventListener('fetch', event => event.respondWith(handle(event))) 12 13async function handle(event) { 14 try { 15 let response = await verifyAndHandle(event); 16 17 // responses returned from cache are immutable, so we recreate them 18 // to set CORS headers 19 response = new Response(response.body, response) 20 response.headers.set('Access-Control-Allow-Origin', '*') 21 22 return response 23 } catch (e) { 24 return new Response('An error occurred!', {status: e.statusCode || 500}) 25 } 26} 27 28async function verifyAndHandle(event) { 29 if (!validRequest(event.request)) { 30 return new Response(null, {status: 400}) 31 } 32 33 if (event.request.method === 'OPTIONS') { 34 return handleOptions(event.request) 35 } 36 37 return handleRequest(event) 38} 39 40function handleOptions(request) { 41 // Make sure the necessary headers are present 42 // for this to be a valid pre-flight request 43 if ( 44 request.headers.get('Origin') !== null && 45 request.headers.get('Access-Control-Request-Method') !== null && 46 request.headers.get('Access-Control-Request-Headers') !== null 47 ) { 48 // Handle CORS pre-flight request 49 return new Response(null, { 50 headers: CORS_HEADERS, 51 }) 52 } else { 53 // Handle standard OPTIONS request 54 return new Response(null, { 55 headers: { 56 Allow: 'GET, HEAD, OPTIONS', 57 }, 58 }) 59 } 60} 61 62async function handleRequest(event) { 63 let cache = caches.default 64 let url = new URL(event.request.url) 65 let static_object_token = url.searchParams.get('token') 66 let headers = new Headers(event.request.headers) 67 68 url.host = ORIGIN_HOSTNAME 69 url = normalizeQuery(url) 70 71 headers.set('X-Gitlab-External-Storage-Token', STORAGE_TOKEN) 72 if (static_object_token !== null) { 73 headers.set('X-Gitlab-Static-Object-Token', static_object_token) 74 } 75 76 let request = new Request(url, { headers: headers }) 77 let cached_response = await cache.match(request) 78 let is_conditional_header_set = headers.has('If-None-Match') 79 80 if (cached_response) { 81 return cached_response 82 } 83 84 // We don't want to override If-None-Match that is set on the original request 85 if (cached_response && !is_conditional_header_set) { 86 headers.set('If-None-Match', cached_response.headers.get('ETag')) 87 } 88 89 let response = await fetch(request, { 90 headers: headers, 91 redirect: 'manual' 92 }) 93 94 if (response.status == 304) { 95 if (is_conditional_header_set) { 96 return response 97 } else { 98 return cached_response 99 } 100 } else if (response.ok) { 101 response = new Response(response.body, response) 102 103 // cache.put will never cache any response with a Set-Cookie header 104 response.headers.delete('Set-Cookie') 105 106 if (CACHE_PRIVATE_OBJECTS) { 107 response.headers.delete('Cache-Control') 108 } 109 110 event.waitUntil(cache.put(request, response.clone())) 111 } 112 113 return response 114} 115 116function normalizeQuery(url) { 117 let searchParams = url.searchParams 118 url = new URL(url.toString().split('?')[0]) 119 120 if (url.pathname.includes('/raw/')) { 121 let inline = searchParams.get('inline') 122 123 if (inline == 'false' || inline == 'true') { 124 url.searchParams.set('inline', inline) 125 } 126 } else if (url.pathname.includes('/-/archive/')) { 127 let append_sha = searchParams.get('append_sha') 128 let path = searchParams.get('path') 129 130 if (append_sha == 'false' || append_sha == 'true') { 131 url.searchParams.set('append_sha', append_sha) 132 } 133 if (path) { 134 url.searchParams.set('path', path) 135 } 136 } 137 138 return url 139} 140 141function validRequest(request) { 142 let url = new URL(request.url) 143 let path = url.pathname 144 145 if (/^(.+)(\/raw\/|\/-\/archive\/)/.test(path)) { 146 return true 147 } 148 149 return false 150}
  3. 使用此脚本创建一个新的 worker。

  4. 复制你的 ORIGIN_HOSTNAMESTORAGE_TOKEN 的值。使用这些值配置静态对象的外部存储