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 +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
|