Class: Api::Registry::RegistryProxiesController
- Inherits:
-
V2::ApiController
- Object
- V2::ApiController
- Api::Registry::RegistryProxiesController
- Defined in:
- app/controllers/katello/api/registry/registry_proxies_controller.rb
Overview
rubocop:disable Metrics/ClassLength
Instance Method Summary collapse
- #authenticate_cert_request ⇒ Object
- #authenticate_from_request(token, redirect_on_failure = true) ⇒ Object
- #authorize_repository_read ⇒ Object
- #authorize_repository_write ⇒ Object
- #catalog ⇒ Object
- #check_blob ⇒ Object
- #check_blob_push_container(props) ⇒ Object
- #check_blob_push_field_syntax(props) ⇒ Object
- #check_blob_push_org_id(props) ⇒ Object
-
#check_blob_push_org_label(props) ⇒ Object
rubocop:disable Metrics/MethodLength.
- #check_blob_push_product_id(props) ⇒ Object
- #check_blob_push_product_label(props) ⇒ Object
- #confirm_settings ⇒ Object
- #container_push_prop_validation(props = nil) ⇒ Object
- #create_container_repo_if_needed ⇒ Object
- #create_manifest ⇒ Object
- #create_tar_file(files, repository, tag) ⇒ Object
- #disable_strong_params ⇒ Object
- #find_host_by_ip ⇒ Object
- #find_readable_repository ⇒ Object
- #find_scope_repository ⇒ Object
- #find_writable_repository ⇒ Object
- #finish_upload_blob ⇒ Object
-
#force_include_layer(repository, digest, layer) ⇒ Object
TODO: Until pulp supports optional upload of layers, include all layers pulp.plan.io/issues/3497.
- #get_manifest_files(repository, manifest) ⇒ Object
- #get_matching_products_from_org(organization, product_label) ⇒ Object
- #get_root_repo_from_product(product, root_repo_name) ⇒ Object
- #handle_revoked_token_auth ⇒ Object
- #header_host_uuid ⇒ Object
- #item_not_found(item) ⇒ Object
- #logger ⇒ Object
- #optional_authorize ⇒ Object
- #parse_blob_push_props(path_string = nil) ⇒ Object
- #ping ⇒ Object
- #process_action(method_name, *args) ⇒ Object
- #pull_blob ⇒ Object
- #pull_manifest ⇒ Object
- #push_manifest ⇒ Object
- #redirect_authorization_headers ⇒ Object
- #redirect_client ⇒ Object
- #registry_authorize ⇒ Object
- #render_podman_error(code, message, status = :bad_request) ⇒ Object
- #repackage_message ⇒ Object
- #request_url ⇒ Object
- #require_user_authorization?(repository = @repository) ⇒ Boolean
- #root_repository ⇒ Object
- #route_name ⇒ Object
- #save_pulp_push_distribution_href(pulp_repo_href) ⇒ Object
- #save_pulp_push_repository_href ⇒ Object
- #save_push_repo_hrefs ⇒ Object
- #ssl_client_authorized?(org_label) ⇒ Boolean
- #start_upload_blob ⇒ Object
- #static_index ⇒ Object
- #static_index_authorize ⇒ Object
- #tags_list ⇒ Object
- #tmp_dir ⇒ Object
- #tmp_file(filename) ⇒ Object
- #token ⇒ Object
- #translated_headers_for_proxy ⇒ Object
- #unauthorized ⇒ Object
- #upload_blob ⇒ Object
- #v1_ping ⇒ Object
- #v1_search ⇒ Object
Methods included from Katello::Authentication::ClientAuthentication
#add_candlepin_version_header, #authenticate_client, #cert_from_request, #cert_present?, #set_client_user
Instance Method Details
#authenticate_cert_request ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 48 def authenticate_cert_request if cert_present? client_cert = ::Cert::RhsmClient.new(cert_from_request) uuid = client_cert.uuid auth = authenticate_client if auth ::User.current = ::User.anonymous_admin @host = Katello::Host::ContentFacet.find_by(uuid: uuid)&.host end end end |
#authenticate_from_request(token, redirect_on_failure = true) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 67 def authenticate_from_request(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&.active? User.current = User.unscoped.find(personal_token.user_id) return true if User.current end elsif token_type == 'Basic' && token return true if if redirect_on_failure return false end elsif header_host_uuid @host = Katello::Host::ContentFacet.find_by(uuid: header_host_uuid)&.host else authenticate_cert_request end return @host.presence end |
#authorize_repository_read ⇒ Object
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 516 def @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_write ⇒ Object
489 490 491 492 493 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 489 def @repository = find_writable_repository return item_not_found(params[:repository]) unless @repository true end |
#catalog ⇒ Object
738 739 740 741 742 743 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 738 def catalog repositories = Repository.readable_docker_catalog(@host).collect do |repository| repository.container_repository_name end render json: { repositories: repositories } end |
#check_blob ⇒ Object
601 602 603 604 605 606 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 601 def check_blob pulp_response = Resources::Registry::Proxy.get(@_request.fullpath, 'Accept' => request.headers['Accept']) response.headers['Content-Type'] = pulp_response.headers[:content_type] if pulp_response.headers[:content_type] response.headers['Content-Length'] = pulp_response.headers[:content_length] if pulp_response.headers[:content_length] head pulp_response.code end |
#check_blob_push_container(props) ⇒ Object
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 353 def check_blob_push_container(props) unless props[:name].present? && props[:name].length > 0 return render_podman_error( "NAME_INVALID", _("Invalid format. Container name cannot be blank."), :bad_request ) end @container_name = props[:name] @container_push_name_format = props[:schema] if @container_push_name_format == "label" @container_path_input = "#{props[:organization]}/#{props[:product]}/#{props[:name]}" else @container_path_input = "id/#{props[:organization]}/#{props[:product]}/#{props[:name]}" end # If the repo already exists, check if the existing push format matches root_repo = get_root_repo_from_product(@product, @container_name).first if !root_repo.nil? && @container_push_name_format != root_repo.container_push_name_format return render_podman_error( "NAME_INVALID", _("Repository name '%{container_name}' already exists in this product using a different naming scheme. Please retry your request with the %{root_repo_container_push_name} format or destroy and recreate the repository using your preferred schema.") % {container_name: @container_name, root_repo_container_push_name: root_repo.container_push_name_format}, :conflict ) end true end |
#check_blob_push_field_syntax(props) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 185 def check_blob_push_field_syntax(props) # check basic url field syntax unless props[:valid_format] return render_podman_error( "NAME_INVALID", _("Invalid format. Container pushes should follow 'organization_label/product_label/name' OR 'id/organization_id/product_id/name' schema."), :bad_request ) end return true end |
#check_blob_push_org_id(props) ⇒ Object
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 251 def check_blob_push_org_id(props) org_id = props[:organization] unless org_id.present? && org_id == org_id.to_i.to_s return render_podman_error( "NAME_INVALID", _("Invalid format. Organization id must be an integer without leading zeros."), :bad_request ) end @organization = Organization.find_by_id(org_id.to_i) if @organization.nil? return render_podman_error( "NAME_UNKNOWN", _("Organization id not found: '%s'") % org_id, :not_found ) end true end |
#check_blob_push_org_label(props) ⇒ Object
rubocop:disable Metrics/MethodLength
198 199 200 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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 198 def check_blob_push_org_label(props) org_label = props[:organization] unless org_label.present? && org_label.length > 0 return render_podman_error( "NAME_INVALID", _("Invalid format. Organization label cannot be blank."), :bad_request ) end org = Organization.where("LOWER(label) = '#{org_label}'") # convert to lowercase # reject ambiguous orgs (possible due to lowercase conversion) if org.length > 1 # Determine if the repo already exists in one of the possible products. If yes, # inform the user they need to destroy the existing repo and use the ID format unless props[:product].blank? || props[:name].blank? org.each do |o| products = get_matching_products_from_org(o, props[:product]) products.each do |prod| root_repos = get_root_repo_from_product(prod, props[:name]) unless root_repos.empty? return render_podman_error( "NAME_INVALID", _("Due to a change in your organizations, this container name has become "\ "ambiguous (org name '%{org_label}'). If you wish to continue using this "\ "container name, destroy the organization in conflict with '%{o_name} (id "\ "%{o_id}). If you wish to keep both orgs, destroy '%{o_label}/%{prod_label}/"\ "%{root_repo_label}' and retry your push using the id format.") % { org_label: org_label, o_name: o.name, o_id: o.id, o_label: o.label, prod_label: prod.label, root_repo_label: root_repos.first.label }, :conflict ) end end end end # Otherwise tell them to try pushing with ID format return render_podman_error( "NAME_INVALID", _("Organization label '%s' is ambiguous. Try using an id-based container name.") % org_label, :conflict ) end if org.length == 0 return render_podman_error( "NAME_UNKNOWN", _("Organization not found: '%s'") % org_label, :not_found ) end @organization = org.first true end |
#check_blob_push_product_id(props) ⇒ Object
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 320 def check_blob_push_product_id(props) prod_id = props[:product] unless prod_id.present? && prod_id == prod_id.to_i.to_s return render_podman_error( "NAME_INVALID", _("Invalid format. Product id must be an integer without leading zeros."), :bad_request ) end @product = @organization.products.find_by_id(prod_id.to_i) if @product.nil? return render_podman_error( "NAME_UNKNOWN", _("Product id not found: '%s'") % prod_id, :not_found ) end true end |
#check_blob_push_product_label(props) ⇒ Object
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 271 def check_blob_push_product_label(props) prod_label = props[:product] unless prod_label.present? && prod_label.length > 0 return render_podman_error( "NAME_INVALID", _("Invalid format. Product label cannot be blank."), :bad_request ) end product = get_matching_products_from_org(@organization, prod_label) # reject ambiguous products (possible due to lowercase conversion) if product.length > 1 # Determine if the repo already exists in one of the possible products. If yes, # inform the user they need to destroy the existing repo and use the ID format unless props[:name].blank? product.each do |prod| root_repos = get_root_repo_from_product(prod, props[:name]) unless root_repos.empty? return render_podman_error( "NAME_INVALID", _("Due to a change in your products, this container name has become ambiguous "\ "(product name '%{prod_label}'). If you wish to continue using this container "\ "name, destroy the product in conflict with '%{prod_name}' (id %{prod_id}). If "\ "you wish to keep both products, destroy '%{org_label}/%{prod_dot_label}/"\ "%{root_repo_label}' and retry your push using the id format.") % { prod_label: prod_label, prod_name: prod.name, prod_id: prod.id, org_label: @organization.label, prod_dot_label: prod.label, root_repo_label: root_repos.first.label }, :conflict ) end end end return render_podman_error( "NAME_INVALID", _("Product label '%s' is ambiguous. Try using an id-based container name.") % prod_label, :conflict ) end if product.length == 0 return render_podman_error( "NAME_UNKNOWN", _("Product not found: '%s'") % prod_label, :not_found ) end @product = product.first true end |
#confirm_settings ⇒ Object
846 847 848 849 850 851 852 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 846 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 |
#container_push_prop_validation(props = nil) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 132 def container_push_prop_validation(props = nil) # Handle validation and repo creation for container pushes before talking to pulp props = parse_blob_push_props if props.nil? return false unless check_blob_push_field_syntax(props) # validate input and find the org and product either using downcase label or id if props[:schema] == "label" return false unless check_blob_push_org_label(props) return false unless check_blob_push_product_label(props) else return false unless check_blob_push_org_id(props) return false unless check_blob_push_product_id(props) end return false unless check_blob_push_container(props) true end |
#create_container_repo_if_needed ⇒ Object
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 384 def create_container_repo_if_needed unless @product.syncable? return render_podman_error( 'DENIED', _("Requested access to '%s' is denied") % @container_name, :not_found ) end if get_root_repo_from_product(@product, @container_name).empty? root = @product.add_repo( name: @container_name, label: @container_name, download_policy: 'immediate', content_type: Repository::DOCKER_TYPE, unprotected: true, is_container_push: true, container_push_name: @container_path_input, container_push_name_format: @container_push_name_format ) sync_task(::Actions::Katello::Repository::CreateContainerPushRoot, root, @container_path_input) end end |
#create_manifest ⇒ Object
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 757 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
801 802 803 804 805 806 807 808 809 810 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 801 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_params ⇒ Object
842 843 844 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 842 def disable_strong_params params.permit! end |
#find_host_by_ip ⇒ Object
121 122 123 124 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 121 def find_host_by_ip host_ip = request.remote_ip @host = ::Host.joins(:primary_interface).where("nics.ip = :host_ip OR nics.ip6 = :host_ip", host_ip: host_ip)&.first end |
#find_readable_repository ⇒ Object
495 496 497 498 499 500 501 502 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 495 def find_readable_repository return nil unless params[:repository] repository = Repository.docker_type.find_by(container_repository_name: params[:repository]) if (repository) repository = Repository.readable_docker_catalog(@host).find_by(container_repository_name: params[:repository]) end repository end |
#find_scope_repository ⇒ Object
834 835 836 837 838 839 840 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 834 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_repository ⇒ Object
485 486 487 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 485 def find_writable_repository Repository.docker_type.syncable.find_by_container_repository_name(params[:repository]) end |
#finish_upload_blob ⇒ Object
670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 670 def finish_upload_blob headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] headers['Content-Range'] = request.headers['Content-Range'] if request.headers['Content-Range'] headers['Content-Length'] = request.headers['Content-Length'] if request.headers['Content-Length'] pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, @_request.body, headers) pulp_response.headers.each do |key, value| response.header[key.to_s] = value end save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end |
#force_include_layer(repository, digest, layer) ⇒ Object
TODO: Until pulp supports optional upload of layers, include all layers pulp.plan.io/issues/3497
822 823 824 825 826 827 828 829 830 831 832 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 822 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
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 773 def get_manifest_files(repository, manifest) files = ['manifest.json'] case manifest['schemaVersion'] when 1 if manifest['fsLayers'] files += manifest['fsLayers'].collect do |layer| layerfile = "#{layer['blobSum'][7..]}.tar" force_include_layer(repository, layer['blobSum'], layerfile) layerfile end end when 2 if manifest['layers'] files += manifest['layers'].collect do |layer| layerfile = "#{layer['digest'][7..]}.tar" force_include_layer(repository, layer['digest'], layerfile) layerfile end end files << "#{manifest['config']['digest'][7..]}.tar" else render_error 'custom_error', :status => :internal_server_error, :locals => { :message => "Unsupported schema #{manifest['schemaVersion']}" } return nil end files end |
#get_matching_products_from_org(organization, product_label) ⇒ Object
340 341 342 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 340 def get_matching_products_from_org(organization, product_label) return organization.products.where("LOWER(label) = '#{product_label}'") # convert to lowercase end |
#get_root_repo_from_product(product, root_repo_name) ⇒ Object
344 345 346 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 344 def get_root_repo_from_product(product, root_repo_name) return product.root_repositories.where(label: root_repo_name) end |
#handle_revoked_token_auth ⇒ Object
569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 569 def handle_revoked_token_auth # If the token was revoked, block all actions as unauthorized except for login. # Login uses basic auth (username/password), while all registry operations use bearer tokens. revoked_token = PersonalAccessToken.where(user_id: User.current.id, name: 'registry', revoked: true).first return true if revoked_token.blank? if params['scope'].blank? && request.headers['Authorization']&.match?(/\ABasic /i) revoked_token.destroy! true else false end end |
#header_host_uuid ⇒ Object
89 90 91 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 89 def header_host_uuid request.headers['HostUUID'] || request.headers['HTTP_HOSTUUID'] || request.get_header('HTTP_HOSTUUID') end |
#item_not_found(item) ⇒ Object
884 885 886 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 884 def item_not_found(item) render_podman_error("NAME_UNKNOWN", _("%s was not found!") % item, :not_found) end |
#logger ⇒ Object
858 859 860 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 858 def logger ::Foreman::Logging.logger('katello/registry_proxy') end |
#optional_authorize ⇒ Object
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 93 def @repository = find_scope_repository if @repository && (@repository.environment.registry_unauthenticated_pull || (@repository.organization.label)) true elsif params['action'] == 'catalog' authenticate_from_request(request.headers['Authorization'], false) elsif (params['action'] == 'token' && params['scope'].blank? && params['account'].blank?) true else end end |
#parse_blob_push_props(path_string = nil) ⇒ Object
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 178 179 180 181 182 183 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 150 def parse_blob_push_props(path_string = nil) # path string should follow one of these formats: # - /v2/{org_label}/{product_label}/{name}/blobs/uploads... # - /v2/id/{org_id}/{product_id}/{name}/blobs/uploads... # - /v2/{org_label}/{product_label}/{name}/manifests/... # - /v2/id/{org_id}/{product_id}/{name}/manifests/... # inputs not matching format will return {valid_format: false} path_string = @_request.fullpath if path_string.nil? segments = path_string.split('/') if segments.length >= 7 && segments[0] == "" && segments[1] == "v2" && segments[2] != "id" && (segments[5] == "blobs" || segments[5] == "manifests") return { valid_format: true, schema: "label", organization: segments[2], product: segments[3], name: segments[4], } elsif segments.length >= 8 && segments[0] == "" && segments[1] == "v2" && segments[2] == "id" && (segments[6] == "blobs" || segments[6] == "manifests") return { valid_format: true, schema: "id", organization: segments[3], product: segments[4], name: segments[5], } else return {valid_format: false} end end |
#ping ⇒ Object
700 701 702 703 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 700 def ping response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' render json: {}, status: :ok end |
#process_action(method_name, *args) ⇒ Object
868 869 870 871 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 868 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_blob ⇒ Object
623 624 625 626 627 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 623 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_manifest ⇒ Object
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 584 def pull_manifest headers = {} env = request.env.select do |key, _value| key.match("^HTTP_.*") end env.each do |header| headers[header[0].split('_')[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 |
#push_manifest ⇒ Object
685 686 687 688 689 690 691 692 693 694 695 696 697 698 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 685 def push_manifest headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] body = @_request.body.read pulp_response = Resources::Registry::Proxy.put(@_request.fullpath, body, headers) pulp_response.headers.each do |key, value| response.header[key.to_s] = value end save_push_repo_hrefs if pulp_response.code.between?(200, 299) # Indexing content is only needed after pushing manifests root_repository.library_instance.index_content head pulp_response.code end |
#redirect_authorization_headers ⇒ Object
60 61 62 63 64 65 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 60 def 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_client ⇒ Object
608 609 610 611 612 613 614 615 616 617 618 619 620 621 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 608 def redirect_client return yield rescue RestClient::Exception => exception if [301, 302, 307].include?(exception.response.code) # Handle aliased hostname on original request redirect_location = URI(exception.response.headers[:location]) redirect_location.host = request.host if request.host.present? redirect_to redirect_location.to_s, allow_other_host: true nil else raise exception end end |
#registry_authorize ⇒ Object
106 107 108 109 110 111 112 113 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 106 def @repository = find_readable_repository return true if ['GET', 'HEAD'].include?(request.method) && @repository && ! return true if authenticate_from_request(request.headers['Authorization']) end |
#render_podman_error(code, message, status = :bad_request) ⇒ Object
873 874 875 876 877 878 879 880 881 882 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 873 def render_podman_error(code, , status = :bad_request) # Renders a podman-compatible error and returns false. # code: uppercase string code from opencontainer error code spec: # https://specs.opencontainers.org/distribution-spec/?v=v1.0.0#DISTRIBUTION-SPEC-140 # message: a custom error string # status: a symbol in the 400 block of the rails response code table: # https://guides.rubyonrails.org/layouts_and_rendering.html#the-status-option render json: {errors: [{code: code, message: }]}, status: status false end |
#repackage_message ⇒ Object
21 22 23 24 25 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 21 def yield ensure response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' end |
#request_url ⇒ Object
854 855 856 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 854 def request_url request.protocol + request.host_with_port end |
#require_user_authorization?(repository = @repository) ⇒ Boolean
504 505 506 507 508 509 510 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 504 def (repository = @repository) !(params['action'] == 'token' && params['scope'].blank? && params['account'].blank?) && (!repository || (!repository.archive? && !repository.environment.registry_unauthenticated_pull && !(repository.organization.label))) end |
#root_repository ⇒ Object
348 349 350 351 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 348 def root_repository @root_repository ||= get_root_repo_from_product(@product, @container_name)&.first @root_repository end |
#route_name ⇒ Object
862 863 864 865 866 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 862 def route_name Engine.routes.router.recognize(request) do |_, params| break params[:action] if params[:action] end end |
#save_pulp_push_distribution_href(pulp_repo_href) ⇒ Object
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 448 def save_pulp_push_distribution_href(pulp_repo_href) instance_repo = root_repository&.library_instance pulp_api = instance_repo.backend_service(SmartProxy.pulp_primary).api instance_repo = root_repository&.library_instance distribution_api_response = pulp_api.container_push_distribution_for_repository(pulp_repo_href) pulp_distribution_href = distribution_api_response&.pulp_href pulp_distribution_prn = distribution_api_response&.prn if pulp_distribution_href.empty? return render_podman_error( "BLOB_UPLOAD_UNKNOWN", _("Could not locate Pulp distribution."), :not_found ) end dist = ::Katello::Pulp3::DistributionReference.where(path: @container_path_input, href: pulp_distribution_href, repository_id: instance_repo.id).first if dist if dist.href != pulp_distribution_href dist.update(href: pulp_distribution_href, prn: pulp_distribution_prn) end else ::Katello::Pulp3::DistributionReference.create!(path: @container_path_input, href: pulp_distribution_href, prn: pulp_distribution_prn, repository_id: instance_repo.id) end end |
#save_pulp_push_repository_href ⇒ Object
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 408 def save_pulp_push_repository_href instance_repo = root_repository&.library_instance unless root_repository.present? && instance_repo.present? return render_podman_error( "BLOB_UPLOAD_UNKNOWN", _("Could not locate local uploaded repository for content indexing."), :not_found ) end pulp_api = instance_repo.backend_service(SmartProxy.pulp_primary).api push_repo_api_response = pulp_api.container_push_repo_for_name(@container_path_input) latest_version_href = push_repo_api_response&.latest_version_href pulp_repo_href = push_repo_api_response&.pulp_href if latest_version_href.empty? || pulp_repo_href.empty? return render_podman_error( "BLOB_UPLOAD_UNKNOWN", _("Could not locate repository properties for content indexing."), :not_found ) end # Fetch version PRN from Pulp API # FIXME: Remove this workaround when https://github.com/pulp/pulpcore/issues/7008 is resolved version_response = pulp_api.repository_versions_api.read(latest_version_href, {fields: 'prn'}) latest_version_prn = version_response&.prn instance_repo.update!(version_href: latest_version_href, version_prn: latest_version_prn) # The Pulp repository should not change after first creation if root_repository.repository_references.empty? ::Katello::Pulp3::RepositoryReference.where(root_repository_id: instance_repo.root_id, content_view_id: instance_repo.content_view.id, repository_href: pulp_repo_href).create! end return pulp_repo_href end |
#save_push_repo_hrefs ⇒ Object
478 479 480 481 482 483 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 478 def save_push_repo_hrefs # After content upload, save Pulp hrefs. pulp_repo_href = save_pulp_push_repository_href return unless pulp_repo_href save_pulp_push_distribution_href(pulp_repo_href) end |
#ssl_client_authorized?(org_label) ⇒ Boolean
512 513 514 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 512 def (org_label) request.headers['HTTP_SSL_CLIENT_VERIFY'] == "SUCCESS" && request.headers['HTTP_SSL_CLIENT_S_DN'] == "O=#{org_label}" end |
#start_upload_blob ⇒ Object
640 641 642 643 644 645 646 647 648 649 650 651 652 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 640 def start_upload_blob headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] headers['Content-Length'] = request.headers['Content-Length'] if request.headers['Content-Length'] pulp_response = Resources::Registry::Proxy.post(@_request.fullpath, @_request.body, headers) pulp_response.headers.each do |key, value| response.header[key.to_s] = value end save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end |
#static_index ⇒ Object
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 888 def static_index repos = Repository.readable_docker_catalog(@host) return render json: { 'Registry' => request_url, 'Results' => [] } if repos.empty? available_container_repo_names = repos.map(&:container_repository_name).compact flatpak_index = (redirect_client { Resources::Registry::Proxy.get(@_request.fullpath, headers) }) begin flatpak_index_json = JSON.parse(flatpak_index) rescue JSON::ParserError => e Rails.logger.error("Failed to parse flatpak index JSON: #{e.}") return render json: { 'Registry' => request_url, 'Results' => [] } end flatpak_index_json['Results'] = flatpak_index_json['Results'].select do |result| available_container_repo_names.include?(result['Name']) end render json: flatpak_index_json end |
#static_index_authorize ⇒ Object
115 116 117 118 119 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 115 def authenticate_from_request(request.headers['Authorization']) find_host_by_ip unless @host true end |
#tags_list ⇒ Object
745 746 747 748 749 750 751 752 753 754 755 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 745 def = @repository..collect do |tag| tag.name end .uniq! .sort! render json: { name: @repository.container_repository_name, tags: , } end |
#tmp_dir ⇒ Object
812 813 814 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 812 def tmp_dir "#{Rails.root}/tmp" end |
#tmp_file(filename) ⇒ Object
816 817 818 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 816 def tmp_file(filename) File.join(tmp_dir, filename) end |
#token ⇒ Object
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 534 def token if ! personal_token = OpenStruct.new(token: 'unauthenticated', issued_at: Time.now, expires_at: 3.minutes.from_now) else PersonalAccessToken.transaction do personal_token = PersonalAccessToken.where(user_id: User.current.id, name: 'registry', revoked: false).first if personal_token.nil? return unless handle_revoked_token_auth personal_token = PersonalAccessToken.new(user: User.current, name: 'registry', expires_at: Setting['registry_token_expiration_minutes'].minutes.from_now) personal_token.generate_token personal_token.save! else personal_token.expires_at = Setting['registry_token_expiration_minutes'].minutes.from_now personal_token.save! end rescue ActiveRecord::RecordInvalid # This rescue block exists to handle the race condition for multiple token requests at once. We reload the token first, THEN respond unauthorized if needed. personal_token = PersonalAccessToken.where(user_id: User.current.id, name: 'registry', revoked: false).reload.first return if personal_token.nil? end end create_time = (personal_token.created_at || personal_token.issued_at).to_time expiry_time = personal_token.expires_at.to_time expiration_seconds = (expiry_time - create_time).to_int # result already in seconds response.headers['Docker-Distribution-API-Version'] = 'registry/2.0' render json: { token: personal_token.token, expires_in: expiration_seconds, issued_at: create_time.rfc3339, } end |
#translated_headers_for_proxy ⇒ Object
629 630 631 632 633 634 635 636 637 638 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 629 def translated_headers_for_proxy current_headers = {} env = request.env.select do |key, _value| key.match("^HTTP_.*") end env.each do |header| current_headers[header[0].split('_')[1..].join('-')] = header[1] end current_headers end |
#unauthorized ⇒ Object
126 127 128 129 130 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 126 def render_error('unauthorized', :status => :unauthorized) false end |
#upload_blob ⇒ Object
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 654 def upload_blob headers = translated_headers_for_proxy headers['Content-Type'] = request.headers['Content-Type'] if request.headers['Content-Type'] headers['Content-Range'] = request.headers['Content-Range'] if request.headers['Content-Range'] headers['Content-Length'] = request.headers['Content-Length'] if request.headers['Content-Length'] body = @_request.body.read pulp_response = Resources::Registry::Proxy.patch(@_request.fullpath, body, headers) pulp_response.headers.each do |key, value| response.header[key.to_s] = value end save_push_repo_hrefs if pulp_response.code.between?(200, 299) head pulp_response.code end |
#v1_ping ⇒ Object
705 706 707 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 705 def v1_ping head :ok end |
#v1_search ⇒ Object
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
# File 'app/controllers/katello/api/registry/registry_proxies_controller.rb', line 709 def v1_search # Checks for v2 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_DOCKER_DISTRIBUTION_API_VERSION'] == 'registry/2.0' render json: {}, status: :not_found return end authenticate # to set current_user, not to enforce = { resource_class: Katello::Repository, } params[:per_page] = params[:n] || 25 params[:search] = params[:q] search_results = scoped_search(Repository.readable_docker_catalog(@host).distinct, :container_repository_name, :asc, ) 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 |