smart_proxy_container_gateway 2.0.0 → 3.1.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 +4 -4
- data/README.md +2 -3
- data/lib/smart_proxy_container_gateway/container_gateway.rb +10 -6
- data/lib/smart_proxy_container_gateway/container_gateway_api.rb +92 -14
- data/lib/smart_proxy_container_gateway/container_gateway_main.rb +22 -14
- data/lib/smart_proxy_container_gateway/database.rb +7 -14
- data/lib/smart_proxy_container_gateway/version.rb +1 -1
- data/settings.d/container_gateway.yml.example +13 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f16bba86dcd701d0d51877ed12b0c44f3c8bb86f135c18fd1babb73bbecb2b9b
|
4
|
+
data.tar.gz: 7dabc1909e9c578020f923f9033a34271fa085f9bb75c212fd9f55aa9f2f7cc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
42
|
-
:
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
40
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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.
|
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
|
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
|
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
|
-
|
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)
|
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)
|
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)
|
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
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
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(
|
8
|
-
|
9
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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,10 +1,16 @@
|
|
1
1
|
---
|
2
2
|
:enabled: true
|
3
|
-
|
4
|
-
:
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
|
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
|
-
|
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:
|
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-
|
11
|
+
date: 2024-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|