Static objects external storage
原文:https://docs.gitlab.com/ee/administration/static_objects_external_storage.html
Static objects external storage
在 GitLab 12.3 中引入 .
可以将 GitLab 配置为从外部存储(例如内容交付网络(CDN))为存储库静态对象(例如,存档或原始 Blob)提供服务.
Configuring
要为静态对象配置外部存储,请执行以下操作:
- 导航到" 管理区域">"设置">"存储库" .
- 展开" 存储库静态对象"部分.
- 输入基本 URL 和任意令牌. 设置外部存储时 ,您将使用一个脚本,将这些值用作
ORIGIN_HOSTNAME
和STORAGE_TOKEN
.
需要使用令牌来区分来自外部存储的请求,因此用户无需绕过外部存储就可以直接前往应用程序. 令牌应在来自外部存储的请求中的X-Gitlab-External-Storage-Token
标头中设置.
Serving private static objects
GitLab 将为属于私有项目的静态对象 URL 附加一个用户特定的令牌,因此可以代表用户对外部存储进行身份验证. 当处理来自外部存储的请求时,GitLab 将在token
查询参数或X-Gitlab-Static-Object-Token
标头中查找X-Gitlab-Static-Object-Token
以检查用户访问所请求对象的能力.
Requests flow example
以下示例显示了用户,GitLab 和 CDN 之间的一系列请求和响应:
sequenceDiagram User->> GitLab:GET /project/-/archive/master.zip GitLab->>用户:302 找到有关 User,GitLab 的注释:位置:https://cdn.com/project/-/archive/master. zip?token =安全用户令牌用户->> CDN:GET /project/-/archive/master.zip?token=安全用户令牌替代对象不在缓存 CDN->> GitLab:GET / project /- /archive/master.zip 关于 CDN,GitLab 的说明:X-Gitlab-External-Storage-Token:secure-cdn-token X-Gitlab-Static-Object-Token:安全用户令牌 GitLab->> CDN:200 OK CDN->>用户:master.zip 其他对象在缓存 CDN->> GitLab:GET / project /-/ archive / master.zip 关于 CDN,GitLab 的说明:X-Gitlab-External-Storage-Token:secure-cdn-token X-Gitlab-Static-Object-Token:安全用户令牌 如果不匹配:etag 值 GitLab->> CDN:304 未修改 CDN->>用户:master.zip 结束
Set up external storage
尽管此过程使用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
值. 使用这些值为静态对象配置外部存储 .