summaryrefslogtreecommitdiff
path: root/app/controllers/groups/dependency_proxy_for_containers_controller.rb
blob: 2e9e0b12d2f5e70ecf64a7d4f137a1aa44c2b20d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# frozen_string_literal: true

class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy::ApplicationController
  include Gitlab::Utils::StrongMemoize
  include DependencyProxy::GroupAccess
  include SendFileUpload
  include ::PackagesHelper # for event tracking
  include WorkhorseRequest

  before_action :ensure_group
  before_action :ensure_token_granted!, only: [:blob, :manifest]
  before_action :ensure_feature_enabled!

  before_action :verify_workhorse_api!, only: [:authorize_upload_blob, :upload_blob, :authorize_upload_manifest, :upload_manifest]
  skip_before_action :verify_authenticity_token, only: [:authorize_upload_blob, :upload_blob, :authorize_upload_manifest, :upload_manifest]

  attr_reader :token

  feature_category :dependency_proxy
  urgency :low

  def manifest
    result = DependencyProxy::FindCachedManifestService.new(group, image, tag, token).execute

    if result[:status] == :success
      if result[:manifest]
        send_manifest(result[:manifest], from_cache: result[:from_cache])
      else
        send_dependency(manifest_header, DependencyProxy::Registry.manifest_url(image, tag), manifest_file_name)
      end
    else
      render status: result[:http_status], json: result[:message]
    end
  end

  def blob
    blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)

    if blob.present?
      event_name = tracking_event_name(object_type: :blob, from_cache: true)
      track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)

      send_upload(blob.file)
    else
      send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
    end
  end

  def authorize_upload_blob
    set_workhorse_internal_api_content_type

    render json: DependencyProxy::FileUploader.workhorse_authorize(has_length: false, maximum_size: DependencyProxy::Blob::MAX_FILE_SIZE)
  end

  def upload_blob
    @group.dependency_proxy_blobs.create!(
      file_name: blob_file_name,
      file: params[:file],
      size: params[:file].size
    )

    event_name = tracking_event_name(object_type: :blob, from_cache: false)
    track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)

    head :ok
  end

  def authorize_upload_manifest
    set_workhorse_internal_api_content_type

    render json: DependencyProxy::FileUploader.workhorse_authorize(has_length: false, maximum_size: DependencyProxy::Manifest::MAX_FILE_SIZE)
  end

  def upload_manifest
    attrs = {
      file_name: manifest_file_name,
      content_type: request.headers[Gitlab::Workhorse::SEND_DEPENDENCY_CONTENT_TYPE_HEADER],
      digest: request.headers[DependencyProxy::Manifest::DIGEST_HEADER],
      file: params[:file],
      size: params[:file].size
    }

    manifest = @group.dependency_proxy_manifests
                     .active
                     .find_by_file_name(manifest_file_name)

    if manifest
      manifest.update!(attrs)
    else
      @group.dependency_proxy_manifests.create!(attrs)
    end

    event_name = tracking_event_name(object_type: :manifest, from_cache: false)
    track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)

    head :ok
  end

  private

  def send_manifest(manifest, from_cache:)
    response.headers[DependencyProxy::Manifest::DIGEST_HEADER] = manifest.digest
    response.headers['Content-Length'] = manifest.size
    response.headers['Docker-Distribution-Api-Version'] = DependencyProxy::DISTRIBUTION_API_VERSION
    response.headers['Etag'] = "\"#{manifest.digest}\""
    content_type = manifest.content_type

    event_name = tracking_event_name(object_type: :manifest, from_cache: from_cache)
    track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)

    send_upload(
      manifest.file,
      proxy: true,
      redirect_params: { query: { 'response-content-type' => content_type } },
      send_params: { type: content_type }
    )
  end

  def blob_file_name
    @blob_file_name ||= params[:sha].sub('sha256:', '') + '.gz'
  end

  def manifest_file_name
    @manifest_file_name ||= Gitlab::Utils.check_path_traversal!("#{image}:#{tag}.json")
  end

  def group
    strong_memoize(:group) do
      Group.find_by_full_path(params[:group_id], follow_redirects: true)
    end
  end

  def image
    params[:image]
  end

  def tag
    params[:tag]
  end

  def tracking_event_name(object_type:, from_cache:)
    event_name = "pull_#{object_type}"
    event_name = "#{event_name}_from_cache" if from_cache

    event_name
  end

  def dependency_proxy
    @dependency_proxy ||= group.dependency_proxy_setting
  end

  def ensure_group
    render_404 unless group
  end

  def ensure_feature_enabled!
    render_404 unless dependency_proxy.enabled
  end

  def ensure_token_granted!
    result = DependencyProxy::RequestTokenService.new(image).execute

    if result[:status] == :success
      @token = result[:token]
    else
      render status: result[:http_status], json: result[:message]
    end
  end

  def token_header
    { Authorization: ["Bearer #{token}"] }
  end

  def manifest_header
    token_header.merge(Accept: ::ContainerRegistry::Client::ACCEPTED_TYPES)
  end
end