smart_proxy_container_gateway 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1965b68bd7795a23fafce6976458a33bf58f8ef67ccab714ab52edcd69558e4e
4
- data.tar.gz: f025588bd31da08bbf6fc2bfd9b58924a4d99d86571b689f6bbfdfc2a7e458dd
3
+ metadata.gz: 470113167c3182629d62ee7babddf775b3e99e4c21ce0df5a9716894867dad86
4
+ data.tar.gz: 7adbe20e3c0dda9a9a3704501144c5f4134c7d9c266f36b19f24e83df09ad997
5
5
  SHA512:
6
- metadata.gz: 8592a29f188ccc45ee9db74cf72579b5c63957ed4a9a64e015c7f3627bd2ee0fdf23e80efad608b690313fe5a4db28f2db0d5660a0a0a894736e500d1aa5bc78
7
- data.tar.gz: eedefbd87696d56163aef9da0b6af8b4061bea74626054d4bf1c78b7d70425a61939db451a6081b79efdb42b17e248f1a8eaf037b5c882026bff7f5a79d87e62
6
+ metadata.gz: b017c8a19c575d9ddcae254500917089eef21739914b5dc73447ba39af9840679028a4401bf15fd1216ae7299b7da7f55c6200a87830d7553fd8b380bc6c821b
7
+ data.tar.gz: e37b37684d5eaace152ab018067f4fa9292a5f5663543e030ed07eba894e59d6486c6b43a0c465c11dd7de65af14599b2e54a22882ff541dd5cb45e1ae381776
@@ -7,9 +7,11 @@ require 'sinatra'
7
7
  require 'smart_proxy_container_gateway/container_gateway'
8
8
  require 'smart_proxy_container_gateway/container_gateway_main'
9
9
  require 'smart_proxy_container_gateway/foreman_api'
10
+ require 'smart_proxy_container_gateway/rhsm_client'
10
11
 
11
12
  module Proxy
12
13
  module ContainerGateway
14
+ # rubocop:disable Metrics/ClassLength
13
15
  class Api < ::Sinatra::Base
14
16
  include ::Proxy::Log
15
17
  helpers ::Proxy::Helpers
@@ -19,6 +21,35 @@ module Proxy
19
21
  inject_attr :database_impl, :database
20
22
  inject_attr :container_gateway_main_impl, :container_gateway_main
21
23
 
24
+ get '/index/static/?' do
25
+ client_cert = ::Cert::RhsmClient.new(cert_from_request) if valid_cert?
26
+ valid_uuid = client_cert&.uuid&.present?
27
+
28
+ pulp_response = container_gateway_main.flatpak_static_index(translated_headers_for_proxy, params)
29
+
30
+ if pulp_response.code.to_i >= 400
31
+ status pulp_response.code.to_i
32
+ body pulp_response.body
33
+ elsif valid_uuid
34
+ host = database.connection[:hosts][{ uuid: client_cert.uuid }]
35
+ if host.nil?
36
+ repo_response = ForemanApi.new.fetch_host_repositories(client_cert.uuid, request.params)
37
+ halt repo_response.code.to_i, repo_response.body unless repo_response.code.to_i == 200
38
+ container_gateway_main.update_host_repositories(client_cert.uuid,
39
+ JSON.parse(repo_response.body)['repositories'])
40
+ end
41
+ catalog = container_gateway_main.host_catalog(client_cert.uuid).select_map(::Sequel[:repositories][:name])
42
+ pulp_index = JSON.parse(pulp_response.body)
43
+ halt 400, "Error: 'Results' key is missing in pulp_index" unless pulp_index.key?("Results")
44
+ pulp_index["Results"].select! { |result| catalog.include?(result["Name"]) }
45
+ status 200
46
+ body pulp_index.to_json
47
+ else
48
+ status pulp_response.code.to_i
49
+ body pulp_response.body
50
+ end
51
+ end
52
+
22
53
  get '/v1/_ping/?' do
23
54
  pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
24
55
  status pulp_response.code.to_i
@@ -26,7 +57,9 @@ module Proxy
26
57
  end
27
58
 
28
59
  get '/v2/?' do
29
- if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
60
+ client_cert = ::Cert::RhsmClient.new(cert_from_request) if valid_cert?
61
+ valid_uuid = client_cert&.uuid&.present?
62
+ if valid_uuid || (auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?))
30
63
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
31
64
  pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
32
65
  status pulp_response.code.to_i
@@ -117,9 +150,18 @@ module Proxy
117
150
  end
118
151
 
119
152
  get '/v2/_catalog/?' do
153
+ client_cert = ::Cert::RhsmClient.new(cert_from_request) if valid_cert?
120
154
  catalog = []
121
- if auth_header.present?
122
- if auth_header.unauthorized_token?
155
+ if client_cert&.uuid&.present?
156
+ host = database.connection[:hosts][{ uuid: client_cert.uuid }]
157
+ if host.nil?
158
+ repo_response = ForemanApi.new.fetch_host_repositories(client_cert.uuid, request.params)
159
+ container_gateway_main.update_host_repositories(client_cert.uuid,
160
+ JSON.parse(repo_response.body)['repositories'])
161
+ end
162
+ catalog = container_gateway_main.host_catalog(client_cert.uuid).select_map(::Sequel[:repositories][:name])
163
+ elsif auth_header.present?
164
+ if auth_header.unauthenticated_token? || auth_header.unauthorized_token?
123
165
  catalog = container_gateway_main.catalog.select_map(::Sequel[:repositories][:name])
124
166
  elsif auth_header.valid_user_token?
125
167
  catalog = container_gateway_main.catalog(auth_header.user).select_map(::Sequel[:repositories][:name])
@@ -150,42 +192,33 @@ module Proxy
150
192
  request.params['account'] ||= username if username.present?
151
193
  end
152
194
 
153
- unless auth_header.present? && auth_header.basic_auth?
154
- return { token: AuthorizationHeader::UNAUTHORIZED_TOKEN, issued_at: Time.now.rfc3339,
155
- expires_in: 1.year.seconds.to_i }.to_json
156
- end
157
-
158
195
  token_response = ForemanApi.new.fetch_token(auth_header.raw_header, request.params)
159
- if token_response.code.to_i != 200
160
- halt token_response.code.to_i, token_response.body
161
- else
162
- # This returned token should follow OAuth2 spec. We need some minor conversion
163
- # to store the token with the expires_at time (using rfc3339).
164
- token_response_body = JSON.parse(token_response.body)
196
+ halt token_response.code.to_i, token_response.body unless token_response.code.to_i == 200
165
197
 
166
- if token_response_body['token'].nil?
167
- halt 502, "Recieved malformed token response"
168
- end
198
+ token_response_body = JSON.parse(token_response.body)
199
+ halt 502, "Recieved malformed token response" if token_response_body['token'].nil?
200
+
201
+ # Check for unauthorized tokens and respond with 401
202
+ halt 401, "unauthorized" if token_response_body['token'] == AuthorizationHeader::UNAUTHORIZED_TOKEN
203
+
204
+ # Skip storing the token if it is unauthenticated
205
+ unless token_response_body['token'] == AuthorizationHeader::UNAUTHENTICATED_TOKEN
169
206
 
170
207
  # "issued_at" is an optional field. Per OAuth2 we assume time of token response as
171
208
  # the issue time if the field is ommitted.
172
209
  token_issue_time = (token_response_body["issued_at"] || token_response["Date"])&.to_time
173
- if token_issue_time.nil?
174
- halt 502, "Recieved malformed token response"
175
- end
210
+ halt 502, "Recieved malformed token response" if token_issue_time.nil?
176
211
 
212
+ # This returned token should follow OAuth2 spec. We need some minor conversion
213
+ # to store the token with the expires_at time (using rfc3339).
177
214
  # 'expires_in' is an optional field. If not provided, assume 60 seconds per OAuth2 spec
178
215
  expires_in = token_response_body.fetch("expires_in", 60)
179
216
  expires_at = token_issue_time + expires_in.seconds
180
- if request.params['account'].present?
181
- container_gateway_main.insert_token(
182
- request.params['account'],
183
- token_response_body['token'],
184
- expires_at.rfc3339
185
- )
186
- else
187
- halt 401, "unauthorized"
188
- end
217
+ container_gateway_main.insert_token(
218
+ request.params['account'],
219
+ token_response_body['token'],
220
+ expires_at.rfc3339
221
+ )
189
222
 
190
223
  repo_response = ForemanApi.new.fetch_user_repositories(auth_header.raw_header, request.params)
191
224
  if repo_response.code.to_i != 200
@@ -194,10 +227,10 @@ module Proxy
194
227
  container_gateway_main.update_user_repositories(request.params['account'],
195
228
  JSON.parse(repo_response.body)['repositories'])
196
229
  end
197
-
198
- # Return the original token response from Katello
199
- return token_response.body
200
230
  end
231
+
232
+ # Return the original token response from Katello
233
+ return token_response.body
201
234
  end
202
235
 
203
236
  get '/users/?' do
@@ -222,12 +255,51 @@ module Proxy
222
255
  {}
223
256
  end
224
257
 
258
+ put '/update_hosts/?' do
259
+ do_authorize_any
260
+ hosts = params['hosts'] || []
261
+ # Refresh hosts table
262
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
263
+ hosts_table = database.connection[:hosts]
264
+ hosts_table.delete
265
+ hosts_table.import(%i[uuid], hosts.map { |host| [host['uuid']] })
266
+ end
267
+ {}
268
+ end
269
+
270
+ put '/host_repository_mapping/?' do
271
+ do_authorize_any
272
+ container_gateway_main.update_host_repo_mapping(params)
273
+ {}
274
+ end
275
+
276
+ put '/update_host_repositories/?' do
277
+ do_authorize_any
278
+ params['hosts'].flat_map do |host_map|
279
+ host_map.filter_map do |host_uuid, repos|
280
+ if repos.nil? || repos.empty?
281
+ repo_names = []
282
+ else
283
+ repo_names = repos
284
+ .select { |repo| repo['auth_required'].to_s.downcase == "true" }
285
+ .map { |repo| repo['repository'] }
286
+ end
287
+ container_gateway_main.update_host_repositories(host_uuid, repo_names)
288
+ end
289
+ end
290
+ {}
291
+ end
292
+
225
293
  private
226
294
 
227
295
  def flatpak_client?
228
296
  request.user_agent&.downcase&.include?('flatpak')
229
297
  end
230
298
 
299
+ def valid_cert?
300
+ cert_from_request.present? && !cert_from_request.empty? && !cert_from_request.include?('null')
301
+ end
302
+
231
303
  def head_or_get_blobs
232
304
  repository = params[:splat][0]
233
305
  digest = params[:splat][1]
@@ -281,6 +353,8 @@ module Proxy
281
353
  end
282
354
 
283
355
  def handle_repo_auth(repository, auth_header, request)
356
+ return if handle_client_cert_auth(repository)
357
+
284
358
  user_token_is_valid = false
285
359
  if auth_header.present? && auth_header.valid_user_token?
286
360
  user_token_is_valid = true
@@ -288,19 +362,33 @@ module Proxy
288
362
  # For flatpak client, header doesn't contain user name. Extract it from token.
289
363
  username ||= container_gateway_main.token_user(@value.split(' ')[1]) if flatpak_client?
290
364
  end
291
- username = request.params['account'] if username.nil?
365
+ username ||= request.params['account']
292
366
 
293
367
  return if container_gateway_main.authorized_for_repo?(repository, user_token_is_valid, username)
294
368
 
369
+ handle_unauthorized_access(username)
370
+ end
371
+
372
+ def handle_unauthorized_access(username)
295
373
  redirect_authorization_headers
374
+ halt 401, "unauthorized" if flatpak_client? && username.nil?
375
+ throw_repo_not_found_error
376
+ end
296
377
 
297
- # If username couldn't be determined from the token or auth_headers
298
- # which is case for first flatpak request, halt with 401 instead of 404
299
- if flatpak_client? && username.nil?
300
- halt 401, "unauthorized"
378
+ def handle_client_cert_auth(repository)
379
+ client_cert = ::Cert::RhsmClient.new(cert_from_request) if valid_cert?
380
+ valid_uuid = client_cert&.uuid&.present?
381
+ if valid_uuid
382
+ host = database.connection[:hosts][{ uuid: client_cert.uuid }]
383
+ if host.nil?
384
+ repo_response = ForemanApi.new.fetch_host_repositories(client_cert.uuid, request.params)
385
+ container_gateway_main.update_host_repositories(client_cert.uuid,
386
+ JSON.parse(repo_response.body)['repositories'])
387
+ end
388
+ halt 401, "unauthorized" unless container_gateway_main.cert_authorized_for_repo?(repository, client_cert.uuid)
389
+ return true
301
390
  end
302
-
303
- throw_repo_not_found_error
391
+ false
304
392
  end
305
393
 
306
394
  def redirect_authorization_headers
@@ -310,6 +398,15 @@ module Proxy
310
398
  "scope=\"repository:registry:pull,push\""
311
399
  end
312
400
 
401
+ def cert_from_request
402
+ request.env['HTTP_X_RHSM_SSL_CLIENT_CERT'] ||
403
+ request.env['SSL_CLIENT_CERT'] ||
404
+ request.env['HTTP_SSL_CLIENT_CERT'] ||
405
+ ENV['HTTP_X_RHSM_SSL_CLIENT_CERT'] ||
406
+ ENV['SSL_CLIENT_CERT'] ||
407
+ ENV['HTTP_SSL_CLIENT_CERT']
408
+ end
409
+
313
410
  def auth_header
314
411
  AuthorizationHeader.new(request.env['HTTP_AUTHORIZATION'])
315
412
  end
@@ -320,6 +417,7 @@ module Proxy
320
417
  inject_attr :database_impl, :database
321
418
  inject_attr :container_gateway_main_impl, :container_gateway_main
322
419
  UNAUTHORIZED_TOKEN = 'unauthorized'.freeze
420
+ UNAUTHENTICATED_TOKEN = 'unauthenticated'.freeze
323
421
 
324
422
  def initialize(value)
325
423
  @value = value || ''
@@ -345,6 +443,10 @@ module Proxy
345
443
  @value.split(' ')[1] == UNAUTHORIZED_TOKEN
346
444
  end
347
445
 
446
+ def unauthenticated_token?
447
+ @value.split(' ')[1] == UNAUTHENTICATED_TOKEN
448
+ end
449
+
348
450
  def token_auth?
349
451
  @value.split(' ')[0] == 'Bearer'
350
452
  end
@@ -367,5 +469,6 @@ module Proxy
367
469
  end
368
470
  end
369
471
  end
472
+ # rubocop:enable Metrics/ClassLength
370
473
  end
371
474
  end
@@ -1,6 +1,7 @@
1
1
  require 'net/http'
2
2
  require 'uri'
3
3
  require 'digest'
4
+ require 'erb'
4
5
  require 'smart_proxy_container_gateway/dependency_injection'
5
6
  require 'sequel'
6
7
  module Proxy
@@ -40,6 +41,14 @@ module Proxy
40
41
  end
41
42
  end
42
43
 
44
+ def flatpak_static_index(headers, params = {})
45
+ uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/index/static")
46
+ unless params.empty?
47
+ uri.query = params.map { |k, v| "#{ERB::Util.url_encode(k.to_s)}=#{ERB::Util.url_encode(v.to_s)}" }.join('&')
48
+ end
49
+ pulp_registry_request(uri, headers)
50
+ end
51
+
43
52
  def ping(headers)
44
53
  uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
45
54
  pulp_registry_request(uri, headers)
@@ -101,6 +110,17 @@ module Proxy
101
110
  end
102
111
  end
103
112
 
113
+ def host_catalog(host_uuid = nil)
114
+ if host_uuid.nil?
115
+ unauthenticated_repos
116
+ else
117
+ database.connection[:repositories].
118
+ left_join(:hosts_repositories, repository_id: :id).
119
+ left_join(:hosts, ::Sequel[:hosts][:id] => :host_id).where(uuid: host_uuid).
120
+ or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
121
+ end
122
+ end
123
+
104
124
  def unauthenticated_repos
105
125
  database.connection[:repositories].where(auth_required: false).order(:name)
106
126
  end
@@ -163,6 +183,66 @@ module Proxy
163
183
  end
164
184
  end
165
185
 
186
+ # Replaces the entire host-repo mapping for all hosts.
187
+ # Assumes host is present in the DB.
188
+ def update_host_repo_mapping(host_repo_maps)
189
+ # Get DB tables
190
+ hosts_repositories = database.connection[:hosts_repositories]
191
+
192
+ # Build list of [repository_id, host_id] pairs
193
+ entries = build_host_repository_mapping(host_repo_maps)
194
+
195
+ # Insert all in a single transaction
196
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
197
+ hosts_repositories.delete
198
+ hosts_repositories.import(%i[repository_id host_id], entries)
199
+ end
200
+ end
201
+
202
+ def build_host_repository_mapping(host_repo_maps)
203
+ hosts = database.connection[:hosts]
204
+ repositories = database.connection[:repositories]
205
+ entries = host_repo_maps['hosts'].flat_map do |host_map|
206
+ host_map.filter_map do |host_uuid, repos|
207
+ host = hosts[{ uuid: host_uuid }]
208
+ next unless host
209
+
210
+ repo_names = repos
211
+ .select { |repo| repo['auth_required'].to_s.downcase == "true" }
212
+ .map { |repo| repo['repository'] }
213
+
214
+ repositories
215
+ .where(name: repo_names, auth_required: true)
216
+ .select(:id)
217
+ .map { |repo| [repo[:id], host[:id]] }
218
+ end
219
+ end
220
+ entries.flatten!(1)
221
+ end
222
+
223
+ def update_host_repositories(uuid, repositories)
224
+ host = find_or_create_host(uuid)
225
+ hosts_repositories = database.connection[:hosts_repositories]
226
+ database.connection.transaction(isolation: :serializable,
227
+ retry_on: [Sequel::SerializationFailure],
228
+ num_retries: 10) do
229
+ hosts_repositories.where(host_id: host[:id]).delete
230
+ return if repositories.nil? || repositories.empty?
231
+
232
+ hosts_repositories.import(
233
+ %i[repository_id host_id],
234
+ database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
235
+ [repo[:id], host[:id]]
236
+ end
237
+ )
238
+ end
239
+ end
240
+
241
+ def find_or_create_host(uuid)
242
+ database.connection[:hosts].insert_conflict(target: :uuid, action: :ignore).insert(uuid: uuid)
243
+ database.connection[:hosts][{ uuid: uuid }]
244
+ end
245
+
166
246
  # Returns:
167
247
  # true if the user is authorized to access the repo, or
168
248
  # false if the user is not authorized to access the repo or if it does not exist
@@ -185,6 +265,20 @@ module Proxy
185
265
  false
186
266
  end
187
267
 
268
+ def cert_authorized_for_repo?(repo_name, uuid)
269
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
270
+ repository = database.connection[:repositories][{ name: repo_name }]
271
+ return false if repository.nil?
272
+ return true unless repository[:auth_required]
273
+
274
+ database.connection[:hosts_repositories]
275
+ .where(repository_id: repository[:id])
276
+ .join(:hosts, id: :host_id)
277
+ .where(Sequel[:hosts][:uuid] => uuid)
278
+ .any?
279
+ end
280
+ end
281
+
188
282
  def token_user(token)
189
283
  database.connection[:users][{
190
284
  id: database.connection[:authentication_tokens].where(token_checksum: checksum(token)).select(:user_id)
@@ -1,15 +1,17 @@
1
1
  require 'uri'
2
+ require 'openssl'
2
3
 
3
4
  module Proxy
4
5
  module ContainerGateway
5
6
  class ForemanApi
6
- def registry_request(auth_header, params, suffix)
7
+ def registry_request(auth_header, params, suffix, uuid: '', cert: false)
7
8
  uri = URI.join(Proxy::SETTINGS.foreman_url, Proxy::ContainerGateway::Plugin.settings.katello_registry_path, suffix)
8
9
  uri.query = process_params(params)
9
10
 
10
11
  req = Net::HTTP::Get.new(uri)
11
- req.add_field('Authorization', auth_header)
12
+ req.add_field('Authorization', auth_header) unless cert
12
13
  req.add_field('Accept', 'application/json')
14
+ req.add_field('HostUUID', uuid) if cert
13
15
  req.content_type = 'application/json'
14
16
  http = Net::HTTP.new(uri.hostname, uri.port)
15
17
  http.use_ssl = true
@@ -29,6 +31,10 @@ module Proxy
29
31
  def fetch_user_repositories(auth_header, params)
30
32
  registry_request(auth_header, params, '_catalog')
31
33
  end
34
+
35
+ def fetch_host_repositories(uuid, params)
36
+ registry_request(nil, params, '_catalog', uuid: uuid, cert: true)
37
+ end
32
38
  end
33
39
  end
34
40
  end
@@ -0,0 +1,33 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Cert
5
+ class RhsmClient
6
+ attr_accessor :cert
7
+
8
+ def initialize(cert)
9
+ self.cert = extract(cert)
10
+ end
11
+
12
+ def uuid
13
+ @uuid ||= @cert.subject.to_a.find { |entry| entry[0] == 'CN' }&.[](1)
14
+ end
15
+
16
+ private
17
+
18
+ def extract(cert)
19
+ raise('Invalid cert provided. Ensure that the provided cert is not empty.') if cert.empty?
20
+
21
+ cert = strip_cert(cert)
22
+ cert = Base64.decode64(cert)
23
+ OpenSSL::X509::Certificate.new(cert)
24
+ end
25
+
26
+ def strip_cert(cert)
27
+ cert = cert.to_s.gsub("-----BEGIN CERTIFICATE-----", "").gsub("-----END CERTIFICATE-----", "")
28
+ cert.delete!(' ')
29
+ cert.delete!("\n")
30
+ cert
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ Sequel.migration do
2
+ up do
3
+ # Create the hosts table
4
+ create_table(:hosts) do
5
+ primary_key :id
6
+ String :uuid, null: false, unique: true
7
+ end
8
+
9
+ # Create the hosts_repositories join table
10
+ create_table(:hosts_repositories) do
11
+ foreign_key :host_id, :hosts, on_delete: :cascade
12
+ foreign_key :repository_id, :repositories, on_delete: :cascade
13
+ primary_key %i[host_id repository_id]
14
+ end
15
+ end
16
+
17
+ down do
18
+ # Drop the hosts_repositories join table
19
+ drop_table(:hosts_repositories)
20
+
21
+ # Drop the hosts table
22
+ drop_table(:hosts)
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module ContainerGateway
3
- VERSION = '3.3.0'.freeze
3
+ VERSION = '3.4.0'.freeze
4
4
  end
5
5
  end
@@ -2,3 +2,4 @@ require 'smart_proxy_container_gateway/version'
2
2
  require 'smart_proxy_container_gateway/database'
3
3
  require 'smart_proxy_container_gateway/container_gateway'
4
4
  require 'smart_proxy_container_gateway/container_gateway_main'
5
+ require 'smart_proxy_container_gateway/rhsm_client'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_container_gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Ballou
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -77,8 +76,8 @@ email: ianballou67@gmail.com
77
76
  executables: []
78
77
  extensions: []
79
78
  extra_rdoc_files:
80
- - README.md
81
79
  - LICENSE
80
+ - README.md
82
81
  files:
83
82
  - LICENSE
84
83
  - README.md
@@ -91,17 +90,18 @@ files:
91
90
  - lib/smart_proxy_container_gateway/database.rb
92
91
  - lib/smart_proxy_container_gateway/dependency_injection.rb
93
92
  - lib/smart_proxy_container_gateway/foreman_api.rb
93
+ - lib/smart_proxy_container_gateway/rhsm_client.rb
94
94
  - lib/smart_proxy_container_gateway/sequel_migrations/001_initial.rb
95
95
  - lib/smart_proxy_container_gateway/sequel_migrations/002_auth_tokens.rb
96
96
  - lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb
97
97
  - lib/smart_proxy_container_gateway/sequel_migrations/004_users_repositories_cascade_delete.rb
98
+ - lib/smart_proxy_container_gateway/sequel_migrations/005_host_repositories.rb
98
99
  - lib/smart_proxy_container_gateway/version.rb
99
100
  - settings.d/container_gateway.yml.example
100
101
  homepage: https://github.com/Katello/smart_proxy_container_gateway
101
102
  licenses:
102
103
  - GPL-3.0-only
103
104
  metadata: {}
104
- post_install_message:
105
105
  rdoc_options: []
106
106
  require_paths:
107
107
  - lib
@@ -116,8 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 3.4.21
120
- signing_key:
119
+ rubygems_version: 3.6.7
121
120
  specification_version: 4
122
121
  summary: Pulp 3 container registry support for Foreman/Katello Smart-Proxy
123
122
  test_files: []