smart_proxy_container_gateway 2.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fee65676d4362e1328671832a7bf3308b193ab1265ea5626eb52d5300ab7d35a
4
- data.tar.gz: fe57f17f732079b2ac27ce70cc7c04ef3ed141283cba58db75676a36dfb4b678
3
+ metadata.gz: f16bba86dcd701d0d51877ed12b0c44f3c8bb86f135c18fd1babb73bbecb2b9b
4
+ data.tar.gz: 7dabc1909e9c578020f923f9033a34271fa085f9bb75c212fd9f55aa9f2f7cc3
5
5
  SHA512:
6
- metadata.gz: b6f0716504c021e799a8a7d31390c9d203c248530073211cf24d7659439b1220647306033c4e60bf98b9425d427b822a8f98b6962f3dab788883bc17b3f5e9a8
7
- data.tar.gz: cfe03eaf3ce5e9ffa23dd355849e81abf249cede80a0f98ac12cf056fc7ec20de81195794e820c44adc9eea9000f176c837b70756670244334b1bbca4690dc54
6
+ metadata.gz: fd70d4d1b20e6f9f37a9c32dd14db3935f8321e723a7a3215cf135e513e4c0d839b1b5c963918c8ce92a19a91fc5a378f23ef427bd3d48cb3a7ffc5f955a0833
7
+ data.tar.gz: 4ce24b3e19d43bd838e1743ff2a8a34f93e00c64ed09c2db64848e6c40640e8dde91f2fa910b0d096bbca0b8055a5f13567b4b821a66359a6b6b84352235d62a
data/README.md CHANGED
@@ -38,9 +38,8 @@ The Container Gateway plugin requires a Pulp 3 instance to connect to. Related
38
38
  SQLite and PostgreSQL are supported, with SQLite being the default for development and testing.
39
39
  Use PostgreSQL in production for improved performance by adding the following settings:
40
40
  ```
41
- # Example PostgreSQL connection settings (default port is 5432)
42
- :database_backend: postgresql
43
- :postgresql_connection_string: postgres://foreman-proxy:changeme@localhost:5432/container_gateway
41
+ # Example PostgreSQL connection settings, using UNIX socket and ident auth
42
+ :db_connection_string: postgres:///container_gateway
44
43
  ```
45
44
 
46
45
  When switching from SQLite to PostgreSQL, if the PostgreSQL database is empty, the SQLite database will be automatically migrated to PostgreSQL.
@@ -7,8 +7,6 @@ module Proxy
7
7
 
8
8
  default_settings :pulp_endpoint => "https://#{`hostname`.strip}",
9
9
  :katello_registry_path => '/v2/',
10
- :database_backend => 'sqlite',
11
- :sqlite_db_path => '/var/lib/foreman-proxy/smart_proxy_container_gateway.db',
12
10
  :sqlite_timeout => 30_000
13
11
 
14
12
  # Load defaults that copy values from SETTINGS. This is done as
@@ -29,10 +27,16 @@ module Proxy
29
27
 
30
28
  load_dependency_injection_wirings do |container_instance, settings|
31
29
  container_instance.singleton_dependency :database_impl, (lambda do
32
- Proxy::ContainerGateway::Database.new(
33
- database_backend: settings[:database_backend], sqlite_db_path: settings[:sqlite_db_path],
34
- sqlite_timeout: settings[:sqlite_timeout], postgresql_connection_string: settings[:postgresql_connection_string]
35
- )
30
+ connection_string = settings.fetch(:db_connection_string) do
31
+ unless settings[:sqlite_db_path]
32
+ raise ValueError, 'Missing db_connection_string or sqlite_db_path option'
33
+ end
34
+
35
+ # Legacy setup
36
+ "sqlite://#{settings[:sqlite_db_path]}?timeout=#{settings[:sqlite_timeout]}"
37
+ end
38
+
39
+ Proxy::ContainerGateway::Database.new(connection_string, settings[:sqlite_db_path])
36
40
  end)
37
41
  container_instance.singleton_dependency :container_gateway_main_impl, (lambda do
38
42
  Proxy::ContainerGateway::ContainerGatewayMain.new(
@@ -19,13 +19,17 @@ module Proxy
19
19
  inject_attr :container_gateway_main_impl, :container_gateway_main
20
20
 
21
21
  get '/v1/_ping/?' do
22
- container_gateway_main.ping
22
+ pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
23
+ status pulp_response.code.to_i
24
+ body pulp_response.body
23
25
  end
24
26
 
25
27
  get '/v2/?' do
26
28
  if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
27
29
  response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
28
- container_gateway_main.ping
30
+ pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
31
+ status pulp_response.code.to_i
32
+ body pulp_response.body
29
33
  else
30
34
  redirect_authorization_headers
31
35
  halt 401, "unauthorized"
@@ -36,22 +40,44 @@ module Proxy
36
40
  repository = params[:splat][0]
37
41
  tag = params[:splat][1]
38
42
  handle_repo_auth(repository, auth_header, request)
39
- redirection_location = container_gateway_main.manifests(repository, tag)
40
- redirect to(redirection_location)
43
+ pulp_response = container_gateway_main.manifests(repository, tag, translated_headers_for_proxy)
44
+ if pulp_response.code.to_i >= 400
45
+ status pulp_response.code.to_i
46
+ body pulp_response.body
47
+ else
48
+ redirection_location = pulp_response['location']
49
+ redirect to(redirection_location)
50
+ end
51
+ end
52
+
53
+ put '/v2/*/manifests/*/?' do
54
+ throw_unsupported_error
41
55
  end
42
56
 
43
57
  get '/v2/*/blobs/*/?' do
44
- repository = params[:splat][0]
45
- digest = params[:splat][1]
46
- handle_repo_auth(repository, auth_header, request)
47
- redirection_location = container_gateway_main.blobs(repository, digest)
48
- redirect to(redirection_location)
58
+ head_or_get_blobs
59
+ end
60
+
61
+ head '/v2/*/blobs/*/?' do
62
+ head_or_get_blobs
63
+ end
64
+
65
+ post '/v2/*/blobs/uploads/?' do
66
+ throw_unsupported_error
67
+ end
68
+
69
+ put '/v2/*/blobs/uploads/*/?' do
70
+ throw_unsupported_error
71
+ end
72
+
73
+ patch '/v2/*/blobs/uploads/*/?' do
74
+ throw_unsupported_error
49
75
  end
50
76
 
51
77
  get '/v2/*/tags/list/?' do
52
78
  repository = params[:splat][0]
53
79
  handle_repo_auth(repository, auth_header, request)
54
- pulp_response = container_gateway_main.tags(repository, params)
80
+ pulp_response = container_gateway_main.tags(repository, translated_headers_for_proxy, params)
55
81
  # "link"=>["<http://pulpcore-api/v2/container-image-name/tags/list?n=100&last=last-tag-name>; rel=\"next\""],
56
82
  # https://docs.docker.com/registry/spec/api/#pagination-1
57
83
  if pulp_response['link'].nil?
@@ -59,14 +85,15 @@ module Proxy
59
85
  else
60
86
  headers['link'] = pulp_response['link']
61
87
  end
62
- pulp_response.body
88
+ status pulp_response.code.to_i
89
+ body pulp_response.body
63
90
  end
64
91
 
65
92
  get '/v1/search/?' do
66
- # Checks for podman client and issues a 404 in that case. Podman
93
+ # Checks for v2 client and issues a 404 in that case. Podman
67
94
  # examines the response from a /v1/search request. If the result
68
95
  # is a 4XX, it will then proceed with a request to /_catalog
69
- if !request.env['HTTP_USER_AGENT'].nil? && request.env['HTTP_USER_AGENT'].downcase.include?('libpod')
96
+ if request.env['HTTP_DOCKER_DISTRIBUTION_API_VERSION'] == 'registry/2.0'
70
97
  halt 404, "not found"
71
98
  end
72
99
 
@@ -181,6 +208,57 @@ module Proxy
181
208
 
182
209
  private
183
210
 
211
+ def head_or_get_blobs
212
+ repository = params[:splat][0]
213
+ digest = params[:splat][1]
214
+ handle_repo_auth(repository, auth_header, request)
215
+ pulp_response = container_gateway_main.blobs(repository, digest, translated_headers_for_proxy)
216
+ if pulp_response.code.to_i >= 400
217
+ status pulp_response.code.to_i
218
+ body pulp_response.body
219
+ else
220
+ redirection_location = pulp_response['location']
221
+ redirect to(redirection_location)
222
+ end
223
+ end
224
+
225
+ def throw_unsupported_error
226
+ content_type :json
227
+ body({
228
+ "errors" => [
229
+ {
230
+ "code" => "UNSUPPORTED",
231
+ "message" => "Pushing content is unsupported"
232
+ }
233
+ ]
234
+ }.to_json)
235
+ halt 404
236
+ end
237
+
238
+ def throw_repo_not_found_error
239
+ content_type :json
240
+ body({
241
+ "errors" => [
242
+ {
243
+ "code" => "NAME_UNKNOWN",
244
+ "message" => "Repository name unknown"
245
+ }
246
+ ]
247
+ }.to_json)
248
+ halt 404
249
+ end
250
+
251
+ def translated_headers_for_proxy
252
+ current_headers = {}
253
+ env = request.env.select do |key, _value|
254
+ key.match("^HTTP_.*")
255
+ end
256
+ env.each do |header|
257
+ current_headers[header[0].split('_')[1..].join('-')] = header[1]
258
+ end
259
+ current_headers
260
+ end
261
+
184
262
  def handle_repo_auth(repository, auth_header, request)
185
263
  user_token_is_valid = false
186
264
  if auth_header.present? && auth_header.valid_user_token?
@@ -192,7 +270,7 @@ module Proxy
192
270
  return if container_gateway_main.authorized_for_repo?(repository, user_token_is_valid, username)
193
271
 
194
272
  redirect_authorization_headers
195
- halt 401, "unauthorized"
273
+ throw_repo_not_found_error
196
274
  end
197
275
 
198
276
  def redirect_authorization_headers
@@ -21,7 +21,7 @@ module Proxy
21
21
  )
22
22
  end
23
23
 
24
- def pulp_registry_request(uri)
24
+ def pulp_registry_request(uri, headers)
25
25
  http_client = Net::HTTP.new(uri.host, uri.port)
26
26
  http_client.ca_file = @pulp_client_ssl_ca
27
27
  http_client.cert = @pulp_client_ssl_cert
@@ -30,30 +30,33 @@ module Proxy
30
30
 
31
31
  http_client.start do |http|
32
32
  request = Net::HTTP::Get.new uri
33
+ headers.each do |key, value|
34
+ request[key] = value
35
+ end
33
36
  http.request request
34
37
  end
35
38
  end
36
39
 
37
- def ping
40
+ def ping(headers)
38
41
  uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
39
- pulp_registry_request(uri).body
42
+ pulp_registry_request(uri, headers)
40
43
  end
41
44
 
42
- def manifests(repository, tag)
45
+ def manifests(repository, tag, headers)
43
46
  uri = URI.parse(
44
47
  "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
45
48
  )
46
- pulp_registry_request(uri)['location']
49
+ pulp_registry_request(uri, headers)
47
50
  end
48
51
 
49
- def blobs(repository, digest)
52
+ def blobs(repository, digest, headers)
50
53
  uri = URI.parse(
51
54
  "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
52
55
  )
53
- pulp_registry_request(uri)['location']
56
+ pulp_registry_request(uri, headers)
54
57
  end
55
58
 
56
- def tags(repository, params = {})
59
+ def tags(repository, headers, params = {})
57
60
  query = "?"
58
61
  unless params[:n].nil? || params[:n] == ""
59
62
  query = "#{query}n=#{params[:n]}"
@@ -64,7 +67,7 @@ module Proxy
64
67
  uri = URI.parse(
65
68
  "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
66
69
  )
67
- pulp_registry_request(uri)
70
+ pulp_registry_request(uri, headers)
68
71
  end
69
72
 
70
73
  def v1_search(params = {})
@@ -157,6 +160,9 @@ module Proxy
157
160
  end
158
161
  end
159
162
 
163
+ # Returns:
164
+ # true if the user is authorized to access the repo, or
165
+ # false if the user is not authorized to access the repo or if it does not exist
160
166
  def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
161
167
  repository = database.connection[:repositories][{ name: repo_name }]
162
168
 
@@ -192,12 +198,14 @@ module Proxy
192
198
  checksum = Digest::SHA256.hexdigest(token)
193
199
  user = Sequel::Model(database.connection[:users]).find_or_create(name: username)
194
200
 
195
- database.connection[:authentication_tokens].where(:token_checksum => checksum).delete
196
- Sequel::Model(database.connection[:authentication_tokens]).
197
- create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
198
- return unless clear_expired_tokens
201
+ database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
202
+ database.connection[:authentication_tokens].where(:token_checksum => checksum).delete
203
+ Sequel::Model(database.connection[:authentication_tokens]).
204
+ create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
205
+ return unless clear_expired_tokens
199
206
 
200
- database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
207
+ database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
208
+ end
201
209
  end
202
210
 
203
211
  private
@@ -4,22 +4,15 @@ module Proxy
4
4
  class Database
5
5
  attr_reader :connection
6
6
 
7
- def initialize(options = {})
8
- if options[:database_backend] == 'sqlite'
9
- @connection = Sequel.connect("sqlite://#{options[:sqlite_db_path]}", timeout: options[:sqlite_timeout])
7
+ def initialize(connection_string, prior_sqlite_db_path = nil)
8
+ @connection = Sequel.connect(connection_string)
9
+ if connection_string.start_with?('sqlite://')
10
10
  @connection.run("PRAGMA foreign_keys = ON;")
11
11
  @connection.run("PRAGMA journal_mode = wal;")
12
- else
13
- unless options[:postgresql_connection_string]
14
- raise ArgumentError, 'PostgreSQL connection string is required'
15
- end
16
-
17
- @connection = Sequel.connect(options[:postgresql_connection_string])
18
- if File.exist?(options[:sqlite_db_path]) &&
19
- (!@connection.table_exists?(:repositories) || @connection[:repositories].count.zero?)
20
- migrate_to_postgres(Sequel.sqlite(options[:sqlite_db_path]), @connection)
21
- File.delete(options[:sqlite_db_path])
22
- end
12
+ elsif prior_sqlite_db_path && File.exist?(prior_sqlite_db_path) &&
13
+ (!@connection.table_exists?(:repositories) || @connection[:repositories].count.zero?)
14
+ migrate_to_postgres(Sequel.sqlite(prior_sqlite_db_path), @connection)
15
+ File.delete(prior_sqlite_db_path)
23
16
  end
24
17
  migrate
25
18
  end
@@ -1,5 +1,5 @@
1
1
  module Proxy
2
2
  module ContainerGateway
3
- VERSION = '2.0.0'.freeze
3
+ VERSION = '3.1.0'.freeze
4
4
  end
5
5
  end
@@ -1,10 +1,16 @@
1
1
  ---
2
2
  :enabled: true
3
- :pulp_endpoint: 'https://your_pulp_3_server_here.com'
4
- :pulp_client_ssl_ca: 'CA Cert for authenticating with Pulp'
5
- :pulp_client_ssl_cert: 'X509 certificate for authenticating with Pulp'
6
- :pulp_client_ssl_key: 'RSA private key for the Pulp certificate'
7
- :katello_registry_path: 'Katello container registry suffix, e.g., /v2/'
8
- :sqlite_db_path: '/var/lib/foreman-proxy/smart_proxy_container_gateway.db'
3
+
4
+ #:pulp_endpoint: 'https://pulp3.example.com'
5
+ #:pulp_client_ssl_ca: '/path/to/ca.pem'
6
+ #:pulp_client_ssl_cert: '/path/to/cert.pem'
7
+ #:pulp_client_ssl_key: '/path/to/key.pem'
8
+
9
+ #:katello_registry_path: '/v2/'
10
+
11
+ #:db_connection_string: postgresql:///container_gateway
12
+
13
+ # Legacy options
14
+ #:sqlite_db_path: '/var/lib/foreman-proxy/smart_proxy_container_gateway.db'
9
15
  # Database busy timeout in milliseconds
10
- :sqlite_timeout: 30000
16
+ #:sqlite_timeout: 30000
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_container_gateway
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Ballou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-15 00:00:00.000000000 Z
11
+ date: 2024-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport