Class: Api::Registry::RegistryProxiesController

Inherits:
V2::ApiController
  • Object
show all
Defined in:
app/controllers/katello/api/registry/registry_proxies_controller.rb

Overview

rubocop:disable Metrics/ClassLength

Instance Method Summary collapse

Instance Method Details

#authorize_repository_readObject



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 117

def authorize_repository_read
  @repository = find_readable_repository
  return item_not_found(params[:repository]) unless @repository

  if params[:tag]
    if params[:tag][0..6] == 'sha256:'
      manifest = Katello::DockerManifestList.where(digest: params[:tag]).first || Katello::DockerManifest.where(digest: params[:tag]).first
      return item_not_found(params[:tag]) unless manifest
    else
      tag = ::Katello::DockerMetaTag.where(id: ::Katello::RepositoryDockerMetaTag.
                                where(repository_id: @repository.id).select(:docker_meta_tag_id), name: params[:tag]).first
      return item_not_found(params[:tag]) unless tag
    end
  end

  true
end

#authorize_repository_writeObject



90
91
92
93
94
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 90

def authorize_repository_write
  @repository = find_writable_repository
  return item_not_found(params[:repository]) unless @repository
  true
end

#cancel_upload_blobObject



286
287
288
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 286

def cancel_upload_blob
  render plain: '', status: :ok
end

#catalogObject



328
329
330
331
332
333
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 328

def catalog
  repositories = Repository.readable_docker_catalog.collect do |repository|
    repository.container_repository_name
  end
  render json: { repositories: repositories }
end

#check_blobObject



171
172
173
174
175
176
177
178
179
180
181
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 171

def check_blob
  begin
    r = Resources::Registry::Proxy.get(@_request.fullpath, 'Accept' => request.headers['Accept'])
    response.header['Content-Length'] = "#{r.body.size}"
  rescue RestClient::NotFound
    digest_file = tmp_file("#{params[:digest][7..-1]}.tar")
    raise unless File.exist? digest_file
    response.header['Content-Length'] = "#{File.size digest_file}"
  end
  render json: {}
end

#chunk_upload_blobObject



248
249
250
251
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 248

def chunk_upload_blob
  response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}"
  render plain: '', status: :accepted
end

#confirm_push_settingsObject



484
485
486
487
488
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 484

def confirm_push_settings
  return true if SETTINGS.dig(:katello, :container_image_registry, :allow_push)
  render_error('custom_error', :status => :not_found,
               :locals => { :message => "Registry push not supported" })
end

#confirm_settingsObject



476
477
478
479
480
481
482
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 476

def confirm_settings
  if SETTINGS.dig(:katello, :container_image_registry) || SmartProxy.pulp_primary&.pulp3_repository_type_support?(::Katello::Repository::DOCKER_TYPE)
    return true
  end
  render_error('custom_error', :status => :not_found,
               :locals => { :message => "Registry not configured" })
end

#create_manifestObject



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 347

def create_manifest
  filename = tmp_file('manifest.json')
  if File.exist? filename
    render_error('custom_error', :status => :unprocessable_entity,
                 :locals => { :message => "Upload already in progress" })
    return nil
  end
  manifest = request.body.read
  File.open(tmp_file('manifest.json'), 'wb', 0600) do |file|
    file.write manifest
  end
  manifest = JSON.parse(manifest)
rescue
  File.delete(tmp_file('manifest.json')) if File.exist? tmp_file('manifest.json')
end

#create_tar_file(files, repository, tag) ⇒ Object



390
391
392
393
394
395
396
397
398
399
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 390

def create_tar_file(files, repository, tag)
  tar_file = "#{repository}_#{tag}.tar"
  `/usr/bin/tar cf #{tmp_file(tar_file)} -C #{tmp_dir} #{files.join(' ')}`

  files.each do |file|
    filename = tmp_file(file)
    File.delete(filename) if File.exist? filename
  end
  tar_file
end

#disable_strong_paramsObject



472
473
474
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 472

def disable_strong_params
  params.permit!
end

#find_readable_repositoryObject



96
97
98
99
100
101
102
103
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 96

def find_readable_repository
  return nil unless params[:repository]
  repository = Repository.docker_type.find_by(container_repository_name: params[:repository])
  if require_user_authorization?(repository)
    repository = Repository.readable_docker_catalog.find_by(container_repository_name: params[:repository])
  end
  repository
end

#find_scope_repositoryObject



464
465
466
467
468
469
470
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 464

def find_scope_repository
  scope = params['scope']
  return nil unless scope

  scopes = scope.split(':')
  scopes[2] == 'pull' ? Repository.docker_type.non_archived.find_by_container_repository_name(scopes[1]) : nil
end

#find_writable_repositoryObject



86
87
88
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 86

def find_writable_repository
  Repository.docker_type.syncable.find_by_container_repository_name(params[:repository])
end

#finish_upload_blobObject



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 269

def finish_upload_blob
  # error by client if no params[:digest]

  uuid_file = tmp_file("#{params[:uuid]}.tar")
  digest_file = tmp_file("#{params[:digest][7..-1]}.tar")

  File.delete(digest_file) if File.exist? digest_file
  File.rename(uuid_file, digest_file)

  response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/#{params[:digest]}"
  response.header['Docker-Content-Digest'] = params[:digest]
  response.header['Content-Range'] = "1-#{File.size(digest_file)}"
  response.header['Content-Length'] = "0"
  response.header['Docker-Upload-UUID'] = params[:uuid]
  head 201
end

#force_include_layer(repository, digest, layer) ⇒ Object

TODO: Until pulp supports optional upload of layers, include all layers pulp.plan.io/issues/3497



452
453
454
455
456
457
458
459
460
461
462
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 452

def force_include_layer(repository, digest, layer)
  unless File.exist? tmp_file(layer)
    logger.debug "Getting blob #{digest} to write to #{layer}"
    fullpath = "/v2/#{repository}/blobs/#{digest}"
    request = Resources::Registry::Proxy.get(fullpath)
    File.open(tmp_file(layer), 'wb', 0600) do |file|
      file.write request.body
    end
    logger.debug "Wrote blob #{digest} to #{layer}"
  end
end

#get_manifest_files(repository, manifest) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 363

def get_manifest_files(repository, manifest)
  files = ['manifest.json']
  if manifest['schemaVersion'] == 1
    if manifest['fsLayers']
      files += manifest['fsLayers'].collect do |layer|
        layerfile = "#{layer['blobSum'][7..-1]}.tar"
        force_include_layer(repository, layer['blobSum'], layerfile)
        layerfile
      end
    end
  elsif manifest['schemaVersion'] == 2
    if manifest['layers']
      files += manifest['layers'].collect do |layer|
        layerfile = "#{layer['digest'][7..-1]}.tar"
        force_include_layer(repository, layer['digest'], layerfile)
        layerfile
      end
    end
    files << "#{manifest['config']['digest'][7..-1]}.tar"
  else
    render_error 'custom_error', :status => :internal_server_error,
                         :locals => { :message => "Unsupported schema #{manifest['schemaVersion']}" }
    return nil
  end
  files
end

#item_not_found(item) ⇒ Object



509
510
511
512
513
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 509

def item_not_found(item)
  msg = "#{item} was not found!"
  # returning errors based on registry specifications in https://docs.docker.com/registry/spec/api/#errors
  render json: {errors: [code: :invalid_request, message: msg, details: msg]}, status: :not_found
end

#loggerObject



494
495
496
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 494

def logger
  ::Foreman::Logging.logger('katello/registry_proxy')
end

#optional_authorizeObject



60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 60

def optional_authorize
  @repository = find_scope_repository
  if @repository && (@repository.environment.registry_unauthenticated_pull || ssl_client_authorized?(@repository.organization.label))
    true
  elsif params['action'] == 'catalog'
    set_user_by_token(request.headers['Authorization'], false)
  elsif (params['action'] == 'token' && params['scope'].blank? && params['account'].blank?)
    true
  else
    authorize
  end
end

#pingObject



290
291
292
293
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 290

def ping
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
  render json: {}, status: :ok
end

#process_action(method_name, *args) ⇒ Object



504
505
506
507
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 504

def process_action(method_name, *args)
  ::Api::V2::BaseController.instance_method(:process_action).bind(self).call(method_name, *args)
  Rails.logger.debug "With body: #{filter_sensitive_data(response.body)}\n" unless route_name == 'pull_blob'
end

#pull_blobObject



194
195
196
197
198
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 194

def pull_blob
  headers = {}
  headers['Accept'] = request.headers['Accept'] if request.headers['Accept']
  redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers, max_redirects: 0) }
end

#pull_manifestObject



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 154

def pull_manifest
  headers = {}
  env = request.env.select do |key, _value|
    key.match("^HTTP_.*")
  end
  env.each do |header|
    headers[header[0].split('_')[1..-1].join('-')] = header[1]
  end

  if (manifest_response = redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers) })
    response.header['Docker-Content-Digest'] = manifest_response.headers[:docker_content_digest]
    response.headers['Content-Type'] = manifest_response.headers[:content_type]
    response.header['Content-Length'] = manifest_response.headers[:content_length]
    render json: manifest_response
  end
end

#pulp_contentObject

FIXME: This is referring to a non-existent Pulp 2 server. Pulp 3 container push support is needed instead.



229
230
231
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 229

def pulp_content
  Katello.pulp_server.resources.content
end

#push_manifestObject

FIXME: Reimplement for Pulp 3.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 201

def push_manifest
  repository = params[:repository]
  tag = params[:tag]

  manifest = create_manifest
  return if manifest.nil?

  begin
    files = get_manifest_files(repository, manifest)
    return if files.nil?

    tar_file = create_tar_file(files, repository, tag)
    return if tar_file.nil?

    digest = upload_manifest(tar_file)
    return if digest.nil?

    tag = upload_tag(digest, tag)
    return if tag.nil?
  ensure
    File.delete(tmp_file('manifest.json')) if File.exist? tmp_file('manifest.json')
  end

  render json: {}
end

#redirect_authorization_headersObject



35
36
37
38
39
40
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 35

def redirect_authorization_headers
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
  response.headers['Www-Authenticate'] = "Bearer realm=\"#{request_url}/v2/token\"," \
                                         "service=\"#{request.host}\"," \
                                         "scope=\"repository:registry:pull,push\""
end

#redirect_clientObject



183
184
185
186
187
188
189
190
191
192
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 183

def redirect_client
  return yield
rescue RestClient::Exception => exception
  if [301, 302, 307].include?(exception.response.code)
    redirect_to exception.response.headers[:location]
    nil
  else
    raise exception
  end
end

#registry_authorizeObject



73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 73

def registry_authorize
  @repository = find_readable_repository
  return true if ['GET', 'HEAD'].include?(request.method) && @repository && !require_user_authorization?

  is_user_set = set_user_by_token(request.headers['Authorization'])

  return true if is_user_set

  redirect_authorization_headers
  render_error('unauthorized', :status => :unauthorized)
  return false
end

#repackage_messageObject



20
21
22
23
24
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 20

def repackage_message
  yield
ensure
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
end

#request_urlObject



490
491
492
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 490

def request_url
  request.protocol + request.host_with_port
end

#require_user_authorization?(repository = @repository) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
110
111
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 105

def require_user_authorization?(repository = @repository)
  !(params['action'] == 'token' && params['scope'].blank? && params['account'].blank?) &&
    (!repository ||
      (!repository.archive? &&
        !repository.environment.registry_unauthenticated_pull &&
        !ssl_client_authorized?(repository.organization.label)))
end

#route_nameObject



498
499
500
501
502
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 498

def route_name
  Engine.routes.router.recognize(request) do |_, params|
    break params[:action] if params[:action]
  end
end

#set_user_by_token(token, redirect_on_failure = true) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 42

def set_user_by_token(token, redirect_on_failure = true)
  if token
    token_type, token = token.split
    if token_type == 'Bearer' && token
      personal_token = PersonalAccessToken.find_by_token(token)
      if personal_token && !personal_token.expired?
        User.current = User.unscoped.find(personal_token.user_id)
        return true if User.current
      end
    elsif token_type == 'Basic' && token
      return true if authorize
      redirect_authorization_headers if redirect_on_failure
      return false
    end
  end
  false
end

#ssl_client_authorized?(org_label) ⇒ Boolean

Returns:

  • (Boolean)


113
114
115
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 113

def ssl_client_authorized?(org_label)
  request.headers['HTTP_SSL_CLIENT_VERIFY'] == "SUCCESS" && request.headers['HTTP_SSL_CLIENT_S_DN'] == "O=#{org_label}"
end

#start_upload_blobObject



233
234
235
236
237
238
239
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 233

def start_upload_blob
  uuid = SecureRandom.hex(16)
  response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{uuid}"
  response.header['Docker-Upload-UUID'] = uuid
  response.header['Range'] = '0-0'
  head 202
end

#status_upload_blobObject



241
242
243
244
245
246
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 241

def status_upload_blob
  response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}"
  response.header['Range'] = "123"
  response.header['Docker-Upload-UUID'] = "123"
  render plain: '', status: :no_content
end

#tags_listObject



335
336
337
338
339
340
341
342
343
344
345
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 335

def tags_list
  tags = @repository.docker_tags.collect do |tag|
    tag.name
  end
  tags.uniq!
  tags.sort!
  render json: {
    name: @repository.container_repository_name,
    tags: tags
  }
end

#tmp_dirObject



442
443
444
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 442

def tmp_dir
  "#{Rails.root}/tmp"
end

#tmp_file(filename) ⇒ Object



446
447
448
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 446

def tmp_file(filename)
  File.join(tmp_dir, filename)
end

#tokenObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 135

def token
  if !require_user_authorization?
    personal_token = OpenStruct.new(token: 'unauthenticated', issued_at: Time.now, expires_at: Time.now + 3)
  else
    personal_token = PersonalAccessToken.where(user_id: User.current.id, name: 'registry').first
    if personal_token.nil?
      personal_token = PersonalAccessToken.new(user: User.current, name: 'registry', expires_at: 6.minutes.from_now)
      personal_token.generate_token
      personal_token.save!
    else
      personal_token.expires_at = 6.minutes.from_now
      personal_token.save!
    end
  end

  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
  render json: { token: personal_token.token, expires_at: personal_token.expires_at, issued_at: personal_token.created_at }
end

#upload_blobObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 253

def upload_blob
  File.open(tmp_file("#{params[:uuid]}.tar"), 'ab', 0600) do |file|
    file.write request.body.read
  end

  # ???? true chunked data?
  if request.headers['Content-Range']
    render_error 'unprocessable_entity', :status => :unprocessable_entity
  end

  response.header['Location'] = "#{request_url}/v2/#{params[:repository]}/blobs/uploads/#{params[:uuid]}"
  response.header['Range'] = "1-#{request.body.size}"
  response.header['Docker-Upload-UUID'] = params[:uuid]
  head 204
end

#upload_manifest(tar_file) ⇒ Object

FIXME: Reimplement for Pulp 3.



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 402

def upload_manifest(tar_file)
  upload_id = pulp_content.create_upload_request['upload_id']
  filename = tmp_file(tar_file)
  uploads = []

  File.open(filename, 'rb') do |file|
    content = file.read
    pulp_content.upload_bits(upload_id, 0, content)

    uploads << {
      id: upload_id,
      name: filename,
      size: file.size,
      checksum: Digest::SHA256.hexdigest(content)
    }
  end

  File.delete(filename)
  task = sync_task(::Actions::Katello::Repository::ImportUpload,
                   @repository, uploads, generate_metadata: true, sync_capsule: true)
  task.output['upload_results'][0]['digest']
ensure
  pulp_content.delete_upload_request(upload_id) if upload_id
end

#upload_tag(digest, tag) ⇒ Object

FIXME: Reimplement for Pulp 3.



428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 428

def upload_tag(digest, tag)
  upload_id = pulp_content.create_upload_request['upload_id']
  uploads = [{
    id: upload_id,
    name: tag,
    digest: digest
  }]
  sync_task(::Actions::Katello::Repository::ImportUpload, @repository, uploads,
            :generate_metadata => true, :sync_capsule => true)
  tag
ensure
  pulp_content.delete_upload_request(upload_id) if upload_id
end

#v1_pingObject



295
296
297
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 295

def v1_ping
  head 200
end

#v1_searchObject



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 299

def v1_search
  # Checks for podman client and issues a 404 in that case. Podman
  # examines the response from a /v1_search request. If the result
  # is a 4XX, it will then proceed with a request to /_catalog
  if request.headers['HTTP_USER_AGENT'].downcase.include?('libpod')
    render json: {}, status: :not_found
    return
  end

  authenticate # to set current_user, not to enforce
  options = {
    resource_class: Katello::Repository
  }
  params[:per_page] = params[:n] || 25
  params[:search] = params[:q]

  search_results = scoped_search(Repository.readable_docker_catalog.distinct,
                                 :container_repository_name, :asc, options)

  results = {
    num_results: search_results[:subtotal],
    query: params[:search]
  }
  results[:results] = search_results[:results].collect do |repository|
    { name: repository[:container_repository_name], description: repository[:description] }
  end
  render json: results, status: :ok
end