Class: Katello::Resources::CDN::CdnResource

Inherits:
Object
  • Object
show all
Defined in:
app/lib/katello/resources/cdn.rb

Direct Known Subclasses

KatelloCdn

Constant Summary collapse

CDN_DOCKER_CONTAINER_LISTING =
"CONTAINER_REGISTRY_LISTING".freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, options = {}) ⇒ CdnResource

Returns a new instance of CdnResource.



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
# File 'app/lib/katello/resources/cdn.rb', line 24

def initialize(url, options = {})
  options.reverse_merge!(:verify_ssl => 9)
  options.assert_valid_keys(:ssl_client_key,
                            :ssl_client_cert,
                            :ssl_ca_file,
                            :ssl_ca_cert,
                            :verify_ssl,
                            :username,
                            :password,
                            :organization_label,
                            :ssl_ca_cert,
                            :custom_cdn)

  @cert_store = OpenSSL::X509::Store.new
  if options[:ssl_ca_cert].present?
    Foreman::Util.add_ca_bundle_to_store(options[:ssl_ca_cert], @cert_store)
  elsif options[:ssl_ca_file]
    @cert_store.add_file(options[:ssl_ca_file])
  else
    @cert_store.set_default_paths
  end

  if proxy&.cacert&.present?
    Foreman::Util.add_ca_bundle_to_store(proxy.cacert, @cert_store)
  end

  @url = url
  @uri = URI.parse(url)
  @options = options
end

Class Method Details

.ca_fileObject



188
189
190
# File 'app/lib/katello/resources/cdn.rb', line 188

def self.ca_file
  "#{Katello::Engine.root}/ca/redhat-uep.pem"
end

.create(cdn_configuration:, product: nil) ⇒ Object



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
# File 'app/lib/katello/resources/cdn.rb', line 55

def self.create(cdn_configuration:, product: nil)
  options = {}
  if cdn_configuration.redhat_cdn?
    options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(product.certificate)
    options[:ssl_client_key] = OpenSSL::PKey.read(product.key)
    options[:ssl_ca_file] = self.ca_file
    self.new(cdn_configuration.url, options)
  elsif cdn_configuration.custom_cdn?
    if cdn_configuration.ssl_ca.present?
      options[:ssl_ca_cert] = cdn_configuration.ssl_ca
    elsif cdn_configuration.redhat_cdn_host?
      options[:ssl_ca_file] = self.ca_file
    end
    if cdn_configuration.custom_cdn_auth_enabled? || cdn_configuration.redhat_cdn_host?
      options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(product.certificate)
      options[:ssl_client_key] = OpenSSL::PKey.read(product.key)
    end
    self.new(cdn_configuration.url, options)
  else
    options[:username] = cdn_configuration.username
    options[:password] = cdn_configuration.password
    options[:organization_label] = cdn_configuration.upstream_organization_label
    options[:content_view_label] = cdn_configuration.upstream_content_view_label
    options[:lifecycle_environment_label] = cdn_configuration.upstream_lifecycle_environment_label
    options[:ssl_ca_cert] = cdn_configuration.ssl_ca
    CDN::KatelloCdn.new(cdn_configuration.url, options)
  end
end

.redhat_cdn?(url) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
# File 'app/lib/katello/resources/cdn.rb', line 88

def self.redhat_cdn?(url)
  url.include?(redhat_cdn_url)
end

.redhat_cdn_urlObject



84
85
86
# File 'app/lib/katello/resources/cdn.rb', line 84

def self.redhat_cdn_url
  SETTINGS[:katello][:redhat_repository_url]
end

Instance Method Details

#fetch_substitutions(base_path) ⇒ Object



181
182
183
184
185
186
# File 'app/lib/katello/resources/cdn.rb', line 181

def fetch_substitutions(base_path)
  get(File.join(base_path, "listing")).split("\n")
rescue Errors::NotFound => e # some of listing file points to not existing content
  log :error, e.message
  [] # return no substitution for unreachable listings
end

#get(path, _headers = {}) ⇒ Object

rubocop:disable Metrics/MethodLength



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
# File 'app/lib/katello/resources/cdn.rb', line 129

def get(path, _headers = {})
  net = http_downloader
  path = File.join(@uri.request_uri, path)
  used_url = File.join("#{@uri.scheme}://#{@uri.host}:#{@uri.port}", path)
  Rails.logger.info "CDN: Requesting path #{used_url}"
  req = Net::HTTP::Get.new(path)

  if @options[:username] && @options[:password]
    req.basic_auth(@options[:username], @options[:password])
  end

  begin
    net.start do |http|
      res = http.request(req, nil) { |http_response| http_response.read_body }
      code = res.code.to_i
      if code == 200
        return res.body
      else
        # we don't really use RestClient here (it doesn't allow to safely
        # set the proxy only for a set of requests and we don't want the
        # backend engines communication to go through the same proxy like
        # accessing CDN - its another use case)
        # But RestClient exceptions are really nice and can be handled in
        # the same way
        exception_class = RestClient::Exceptions::EXCEPTIONS_MAP[code] || RestClient::RequestFailed
        fail exception_class.new(nil, code)
      end
    end
  rescue SocketError
    raise _("Couldn't establish a connection to %s") % @uri
  rescue EOFError
    raise RestClient::ServerBrokeConnection
  rescue Timeout::Error
    raise RestClient::RequestTimeout
  rescue RestClient::ResourceNotFound
    raise Errors::NotFound, _("CDN loading error: %s not found") % used_url
  rescue RestClient::Unauthorized
    raise Errors::SecurityViolation, _("CDN loading error: access denied to %s") % used_url
  rescue RestClient::Forbidden
    raise Errors::SecurityViolation, _("CDN loading error: access forbidden to %s") % used_url
  end
end

#get_container_listings(content_path) ⇒ Object



206
207
208
# File 'app/lib/katello/resources/cdn.rb', line 206

def get_container_listings(content_path)
  JSON.parse(get(File.join(content_path, CdnResource::CDN_DOCKER_CONTAINER_LISTING)))
end

#get_docker_registries(content_path) ⇒ Object

eg content url listing file -> /content/dist/rhel/server/7/7Server/x86_64/containers/CONTAINER_REGISTRY_LISTING format { "header": { "version": "1.0" }, "payload": { "registries": [ { "name": "rhel", "url": "", }, { "name": "rhel7", "url": "test.com:5000/rhel" "aliases": [ "redhat/rhel7" ] } ] } }



229
230
231
232
233
234
235
236
237
238
# File 'app/lib/katello/resources/cdn.rb', line 229

def get_docker_registries(content_path)
  docker_listing = get_container_listings(content_path)
  docker_listing.try(:[], "payload").try(:[], "registries") || []
rescue ::Katello::Errors::NotFound => e # some of listing file points to not existing content
  # If the container listing file was not found
  # there is probably no content to be had.
  Rails.logger.warn("Could not get to #{content_path}.")
  Rails.logger.warn e.to_s
  []
end

#http_downloaderObject



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
# File 'app/lib/katello/resources/cdn.rb', line 96

def http_downloader
  net = net_http_class.new(@uri.host, @uri.port)
  net.use_ssl = @uri.is_a?(URI::HTTPS)

  if @uri.is_a?(URI::HTTPS)
    net.cert_store = @cert_store
    net.cert = @options[:ssl_client_cert]
    net.key = @options[:ssl_client_key]
    net.cert_store = @cert_store
  end

  # NOTE: This is only here due to https://github.com/ruby/openssl/issues/709, otherwise the
  # system-wide crypto policy could be used. Enforcing TLS version >= 1.2  will prevent using
  # very old infrastructure for now, but that was considered better than having an insecure default.
  net.min_version = OpenSSL::SSL::TLS1_2_VERSION

  case @options[:verify_ssl]
  when false, OpenSSL::SSL::VERIFY_NONE
    net.verify_mode = OpenSSL::SSL::VERIFY_NONE
  when Integer
    net.verify_mode = @options[:verify_ssl]
    net.verify_callback = lambda do |preverify_ok, ssl_context|
      if !preverify_ok || ssl_context.error != 0
        err_msg = "SSL Verification failed -- Preverify: #{preverify_ok}, Error: #{ssl_context.error_string} (#{ssl_context.error})"
        fail RestClient::SSLCertificateNotVerified, err_msg
      end
      true
    end
  end
  net
end

#log(level, *args) ⇒ Object



240
241
242
# File 'app/lib/katello/resources/cdn.rb', line 240

def log(level, *args)
  [Rails.logger, @logger].compact.each { |logger| logger.send(level, *args) }
end

#net_http_classObject



192
193
194
195
196
197
198
199
# File 'app/lib/katello/resources/cdn.rb', line 192

def net_http_class
  if proxy
    uri = URI(proxy.url) #Net::HTTP::Proxy ignores port as part of the url
    Net::HTTP::Proxy(uri.hostname, uri.port, proxy.username, proxy.password)
  else
    Net::HTTP
  end
end

#parse_host(host_or_url) ⇒ Object



201
202
203
204
# File 'app/lib/katello/resources/cdn.rb', line 201

def parse_host(host_or_url)
  uri = URI.parse(host_or_url)
  return uri.host || uri.path
end

#proxyObject



92
93
94
# File 'app/lib/katello/resources/cdn.rb', line 92

def proxy
  ::HttpProxy.default_global_content_proxy
end

#substitutorObject



20
21
22
# File 'app/lib/katello/resources/cdn.rb', line 20

def substitutor
  @substitutor ||= Util::CdnVarSubstitutor.new(self)
end

#valid_path?(path, postfix) ⇒ Boolean

rubocop:enable Metrics/MethodLength

Returns:

  • (Boolean)


173
174
175
176
177
178
179
# File 'app/lib/katello/resources/cdn.rb', line 173

def valid_path?(path, postfix)
  get(File.join(path, postfix)).present?
rescue RestClient::MovedPermanently
  return true
rescue Errors::NotFound
  return false
end