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

要为静态对象配置外部存储,请执行以下操作:

  1. 导航到” 管理区域”>”设置”>”存储库” .
  2. 展开” 存储库静态对象”部分.
  3. 输入基本 URL 和任意令牌. 设置外部存储时 ,您将使用一个脚本,将这些值用作ORIGIN_HOSTNAMESTORAGE_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 /-/ a​​rchive / 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)系统应使用相同的原理工作.

  1. 如果还没有,请选择一个 CloudFlare Worker 域.
  2. 在以下脚本中,为前两个常量设置以下值:

    • ORIGIN_HOSTNAME :GitLab 安装的主机名.
    • STORAGE_TOKEN :任何任意的安全令牌(例如,您可以通过在 UNIX 计算机上运行pwgen -cn1 64来获得一个). 按照配置部分中的说明将此令牌保存到管理面板.

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

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