smart_proxy_container_gateway 1.2.0 → 3.0.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 +13 -2
- data/lib/smart_proxy_container_gateway/container_gateway.rb +21 -1
- data/lib/smart_proxy_container_gateway/container_gateway_api.rb +62 -24
- data/lib/smart_proxy_container_gateway/container_gateway_main.rb +107 -116
- data/lib/smart_proxy_container_gateway/database.rb +46 -0
- data/lib/smart_proxy_container_gateway/dependency_injection.rb +10 -0
- data/lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb +2 -2
- data/lib/smart_proxy_container_gateway/sequel_migrations/004_users_repositories_cascade_delete.rb +28 -0
- data/lib/smart_proxy_container_gateway/version.rb +1 -1
- data/lib/smart_proxy_container_gateway.rb +2 -0
- data/settings.d/container_gateway.yml.example +13 -7
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb4e40d814ff330008ad5f1bf4bf667ba9e91415bc997ea4d66a2beb7fb0a00e
|
4
|
+
data.tar.gz: 84360d1228198e91460a2b841985b12c5c9f5d5d062d191862a2e9cc5bef10e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37997d8de598e480912ecc0785fc755138aa90163dcb20981210eff4a935ae85a49a157b8c51189a6a13a6a29a1ff391429e477257d466b0b9d0d19d04d33fcd
|
7
|
+
data.tar.gz: 5a81a246030ed8d9fcd7b847c30fb241c2974e203ae09b256a017432cffa549073430a203e5d6231f55527db005237477a4903482c88ddad47e4c2ca894f8def
|
data/README.md
CHANGED
@@ -35,11 +35,22 @@ The Container Gateway plugin requires a Pulp 3 instance to connect to. Related
|
|
35
35
|
|
36
36
|
# Database information
|
37
37
|
|
38
|
-
SQLite
|
38
|
+
SQLite and PostgreSQL are supported, with SQLite being the default for development and testing.
|
39
|
+
Use PostgreSQL in production for improved performance by adding the following settings:
|
40
|
+
```
|
41
|
+
# Example PostgreSQL connection settings, using UNIX socket and ident auth
|
42
|
+
:db_connection_string: postgres:///container_gateway
|
43
|
+
```
|
44
|
+
|
45
|
+
When switching from SQLite to PostgreSQL, if the PostgreSQL database is empty, the SQLite database will be automatically migrated to PostgreSQL.
|
46
|
+
For the migration to work, the sqlite_db_path setting must point to the old SQLite database file if the default (no setting definition) was not used.
|
47
|
+
The SQLite database file will be deleted after the migration to PostgreSQL is complete.
|
48
|
+
|
49
|
+
Database migrations are completely automated. The plugin checks if the database is up-to-date at initialization time.
|
39
50
|
|
40
51
|
# Katello interaction
|
41
52
|
|
42
|
-
Auth information is retrieved from the Katello server during smart proxy sync time and cached in the
|
53
|
+
Auth information is retrieved from the Katello server during smart proxy sync time and cached in the database.
|
43
54
|
|
44
55
|
Logging in with a container client will cause the Container Gateway to fetch a token from Katello using the login information.
|
45
56
|
|
@@ -7,7 +7,6 @@ module Proxy
|
|
7
7
|
|
8
8
|
default_settings :pulp_endpoint => "https://#{`hostname`.strip}",
|
9
9
|
:katello_registry_path => '/v2/',
|
10
|
-
:sqlite_db_path => '/var/lib/foreman-proxy/smart_proxy_container_gateway.db',
|
11
10
|
:sqlite_timeout => 30_000
|
12
11
|
|
13
12
|
# Load defaults that copy values from SETTINGS. This is done as
|
@@ -25,6 +24,27 @@ module Proxy
|
|
25
24
|
validate :pulp_endpoint, url: true
|
26
25
|
|
27
26
|
rackup_path File.join(__dir__, 'container_gateway_http_config.ru')
|
27
|
+
|
28
|
+
load_dependency_injection_wirings do |container_instance, settings|
|
29
|
+
container_instance.singleton_dependency :database_impl, (lambda do
|
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])
|
40
|
+
end)
|
41
|
+
container_instance.singleton_dependency :container_gateway_main_impl, (lambda do
|
42
|
+
Proxy::ContainerGateway::ContainerGatewayMain.new(
|
43
|
+
database: container_instance.get_dependency(:database_impl),
|
44
|
+
**settings.slice(:pulp_endpoint, :pulp_client_ssl_ca, :pulp_client_ssl_cert, :pulp_client_ssl_key)
|
45
|
+
)
|
46
|
+
end)
|
47
|
+
end
|
28
48
|
end
|
29
49
|
end
|
30
50
|
end
|
@@ -6,7 +6,6 @@ require 'sinatra'
|
|
6
6
|
require 'smart_proxy_container_gateway/container_gateway'
|
7
7
|
require 'smart_proxy_container_gateway/container_gateway_main'
|
8
8
|
require 'smart_proxy_container_gateway/foreman_api'
|
9
|
-
require 'sqlite3'
|
10
9
|
|
11
10
|
module Proxy
|
12
11
|
module ContainerGateway
|
@@ -14,15 +13,23 @@ module Proxy
|
|
14
13
|
include ::Proxy::Log
|
15
14
|
helpers ::Proxy::Helpers
|
16
15
|
helpers ::Sinatra::Authorization::Helpers
|
16
|
+
extend ::Proxy::ContainerGateway::DependencyInjection
|
17
|
+
|
18
|
+
inject_attr :database_impl, :database
|
19
|
+
inject_attr :container_gateway_main_impl, :container_gateway_main
|
17
20
|
|
18
21
|
get '/v1/_ping/?' do
|
19
|
-
|
22
|
+
pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
|
23
|
+
status pulp_response.code.to_i
|
24
|
+
body pulp_response.body
|
20
25
|
end
|
21
26
|
|
22
27
|
get '/v2/?' do
|
23
28
|
if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
|
24
29
|
response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
|
25
|
-
|
30
|
+
pulp_response = container_gateway_main.ping(translated_headers_for_proxy)
|
31
|
+
status pulp_response.code.to_i
|
32
|
+
body pulp_response.body
|
26
33
|
else
|
27
34
|
redirect_authorization_headers
|
28
35
|
halt 401, "unauthorized"
|
@@ -33,22 +40,34 @@ module Proxy
|
|
33
40
|
repository = params[:splat][0]
|
34
41
|
tag = params[:splat][1]
|
35
42
|
handle_repo_auth(repository, auth_header, request)
|
36
|
-
|
37
|
-
|
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
|
38
51
|
end
|
39
52
|
|
40
53
|
get '/v2/*/blobs/*/?' do
|
41
54
|
repository = params[:splat][0]
|
42
55
|
digest = params[:splat][1]
|
43
56
|
handle_repo_auth(repository, auth_header, request)
|
44
|
-
|
45
|
-
|
57
|
+
pulp_response = container_gateway_main.blobs(repository, digest, translated_headers_for_proxy)
|
58
|
+
if pulp_response.code.to_i >= 400
|
59
|
+
status pulp_response.code.to_i
|
60
|
+
body pulp_response.body
|
61
|
+
else
|
62
|
+
redirection_location = pulp_response['location']
|
63
|
+
redirect to(redirection_location)
|
64
|
+
end
|
46
65
|
end
|
47
66
|
|
48
67
|
get '/v2/*/tags/list/?' do
|
49
68
|
repository = params[:splat][0]
|
50
69
|
handle_repo_auth(repository, auth_header, request)
|
51
|
-
pulp_response =
|
70
|
+
pulp_response = container_gateway_main.tags(repository, translated_headers_for_proxy, params)
|
52
71
|
# "link"=>["<http://pulpcore-api/v2/container-image-name/tags/list?n=100&last=last-tag-name>; rel=\"next\""],
|
53
72
|
# https://docs.docker.com/registry/spec/api/#pagination-1
|
54
73
|
if pulp_response['link'].nil?
|
@@ -56,7 +75,8 @@ module Proxy
|
|
56
75
|
else
|
57
76
|
headers['link'] = pulp_response['link']
|
58
77
|
end
|
59
|
-
pulp_response.
|
78
|
+
status pulp_response.code.to_i
|
79
|
+
body pulp_response.body
|
60
80
|
end
|
61
81
|
|
62
82
|
get '/v1/search/?' do
|
@@ -74,19 +94,23 @@ module Proxy
|
|
74
94
|
end
|
75
95
|
params[:user] = username
|
76
96
|
end
|
77
|
-
repositories =
|
97
|
+
repositories = container_gateway_main.v1_search(params)
|
78
98
|
|
79
99
|
content_type :json
|
80
|
-
{
|
100
|
+
{
|
101
|
+
num_results: repositories.size,
|
102
|
+
query: params[:q],
|
103
|
+
results: repositories.map { |repo_name| { description: '', name: repo_name } }
|
104
|
+
}.to_json
|
81
105
|
end
|
82
106
|
|
83
107
|
get '/v2/_catalog/?' do
|
84
108
|
catalog = []
|
85
109
|
if auth_header.present?
|
86
110
|
if auth_header.unauthorized_token?
|
87
|
-
catalog =
|
111
|
+
catalog = container_gateway_main.catalog.select_map(::Sequel[:repositories][:name])
|
88
112
|
elsif auth_header.valid_user_token?
|
89
|
-
catalog =
|
113
|
+
catalog = container_gateway_main.catalog(auth_header.user).select_map(::Sequel[:repositories][:name])
|
90
114
|
else
|
91
115
|
redirect_authorization_headers
|
92
116
|
halt 401, "unauthorized"
|
@@ -131,7 +155,7 @@ module Proxy
|
|
131
155
|
expires_in = token_response_body.fetch("expires_in", 60)
|
132
156
|
expires_at = token_issue_time + expires_in.seconds
|
133
157
|
|
134
|
-
|
158
|
+
container_gateway_main.insert_token(
|
135
159
|
request.params['account'],
|
136
160
|
token_response_body['token'],
|
137
161
|
expires_at.rfc3339
|
@@ -141,8 +165,8 @@ module Proxy
|
|
141
165
|
if repo_response.code.to_i != 200
|
142
166
|
halt repo_response.code.to_i, repo_response.body
|
143
167
|
else
|
144
|
-
|
145
|
-
|
168
|
+
container_gateway_main.update_user_repositories(request.params['account'],
|
169
|
+
JSON.parse(repo_response.body)['repositories'])
|
146
170
|
end
|
147
171
|
|
148
172
|
# Return the original token response from Katello
|
@@ -154,13 +178,13 @@ module Proxy
|
|
154
178
|
do_authorize_any
|
155
179
|
|
156
180
|
content_type :json
|
157
|
-
{ users:
|
181
|
+
{ users: database.connection[:users].map(:name) }.to_json
|
158
182
|
end
|
159
183
|
|
160
184
|
put '/user_repository_mapping/?' do
|
161
185
|
do_authorize_any
|
162
186
|
|
163
|
-
|
187
|
+
container_gateway_main.update_user_repo_mapping(params)
|
164
188
|
{}
|
165
189
|
end
|
166
190
|
|
@@ -168,22 +192,32 @@ module Proxy
|
|
168
192
|
do_authorize_any
|
169
193
|
|
170
194
|
repositories = params['repositories'].nil? ? [] : params['repositories']
|
171
|
-
|
195
|
+
container_gateway_main.update_repository_list(repositories)
|
172
196
|
{}
|
173
197
|
end
|
174
198
|
|
175
199
|
private
|
176
200
|
|
201
|
+
def translated_headers_for_proxy
|
202
|
+
current_headers = {}
|
203
|
+
env = request.env.select do |key, _value|
|
204
|
+
key.match("^HTTP_.*")
|
205
|
+
end
|
206
|
+
env.each do |header|
|
207
|
+
current_headers[header[0].split('_')[1..].join('-')] = header[1]
|
208
|
+
end
|
209
|
+
current_headers
|
210
|
+
end
|
211
|
+
|
177
212
|
def handle_repo_auth(repository, auth_header, request)
|
178
213
|
user_token_is_valid = false
|
179
|
-
# FIXME: Getting unauthenticated token here...
|
180
214
|
if auth_header.present? && auth_header.valid_user_token?
|
181
215
|
user_token_is_valid = true
|
182
|
-
username = auth_header.user
|
216
|
+
username = auth_header.user[:name]
|
183
217
|
end
|
184
218
|
username = request.params['account'] if username.nil?
|
185
219
|
|
186
|
-
return if
|
220
|
+
return if container_gateway_main.authorized_for_repo?(repository, user_token_is_valid, username)
|
187
221
|
|
188
222
|
redirect_authorization_headers
|
189
223
|
halt 401, "unauthorized"
|
@@ -201,6 +235,10 @@ module Proxy
|
|
201
235
|
end
|
202
236
|
|
203
237
|
class AuthorizationHeader
|
238
|
+
extend ::Proxy::ContainerGateway::DependencyInjection
|
239
|
+
|
240
|
+
inject_attr :database_impl, :database
|
241
|
+
inject_attr :container_gateway_main_impl, :container_gateway_main
|
204
242
|
UNAUTHORIZED_TOKEN = 'unauthorized'.freeze
|
205
243
|
|
206
244
|
def initialize(value)
|
@@ -208,11 +246,11 @@ module Proxy
|
|
208
246
|
end
|
209
247
|
|
210
248
|
def user
|
211
|
-
|
249
|
+
container_gateway_main.token_user(@value.split(' ')[1])
|
212
250
|
end
|
213
251
|
|
214
252
|
def valid_user_token?
|
215
|
-
token_auth? &&
|
253
|
+
token_auth? && container_gateway_main.valid_token?(@value.split(' ')[1])
|
216
254
|
end
|
217
255
|
|
218
256
|
def raw_header
|
@@ -1,47 +1,62 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'uri'
|
3
3
|
require 'digest'
|
4
|
+
require 'smart_proxy_container_gateway/dependency_injection'
|
4
5
|
require 'sequel'
|
5
6
|
module Proxy
|
6
7
|
module ContainerGateway
|
7
8
|
extend ::Proxy::Util
|
8
9
|
extend ::Proxy::Log
|
9
10
|
|
10
|
-
class
|
11
|
-
|
12
|
-
|
11
|
+
class ContainerGatewayMain
|
12
|
+
attr_reader :database
|
13
|
+
|
14
|
+
def initialize(database:, pulp_endpoint:, pulp_client_ssl_ca:, pulp_client_ssl_cert:, pulp_client_ssl_key:)
|
15
|
+
@database = database
|
16
|
+
@pulp_endpoint = pulp_endpoint
|
17
|
+
@pulp_client_ssl_ca = pulp_client_ssl_ca
|
18
|
+
@pulp_client_ssl_cert = OpenSSL::X509::Certificate.new(File.read(pulp_client_ssl_cert))
|
19
|
+
@pulp_client_ssl_key = OpenSSL::PKey::RSA.new(
|
20
|
+
File.read(pulp_client_ssl_key)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pulp_registry_request(uri, headers)
|
13
25
|
http_client = Net::HTTP.new(uri.host, uri.port)
|
14
|
-
http_client.ca_file =
|
15
|
-
http_client.cert =
|
16
|
-
http_client.key =
|
26
|
+
http_client.ca_file = @pulp_client_ssl_ca
|
27
|
+
http_client.cert = @pulp_client_ssl_cert
|
28
|
+
http_client.key = @pulp_client_ssl_key
|
17
29
|
http_client.use_ssl = true
|
18
30
|
|
19
31
|
http_client.start do |http|
|
20
32
|
request = Net::HTTP::Get.new uri
|
33
|
+
headers.each do |key, value|
|
34
|
+
request[key] = value
|
35
|
+
end
|
21
36
|
http.request request
|
22
37
|
end
|
23
38
|
end
|
24
39
|
|
25
|
-
def ping
|
26
|
-
uri = URI.parse("#{
|
27
|
-
pulp_registry_request(uri)
|
40
|
+
def ping(headers)
|
41
|
+
uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
|
42
|
+
pulp_registry_request(uri, headers)
|
28
43
|
end
|
29
44
|
|
30
|
-
def manifests(repository, tag)
|
45
|
+
def manifests(repository, tag, headers)
|
31
46
|
uri = URI.parse(
|
32
|
-
"#{
|
47
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
|
33
48
|
)
|
34
|
-
pulp_registry_request(uri)
|
49
|
+
pulp_registry_request(uri, headers)
|
35
50
|
end
|
36
51
|
|
37
|
-
def blobs(repository, digest)
|
52
|
+
def blobs(repository, digest, headers)
|
38
53
|
uri = URI.parse(
|
39
|
-
"#{
|
54
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
|
40
55
|
)
|
41
|
-
pulp_registry_request(uri)
|
56
|
+
pulp_registry_request(uri, headers)
|
42
57
|
end
|
43
58
|
|
44
|
-
def tags(repository, params = {})
|
59
|
+
def tags(repository, headers, params = {})
|
45
60
|
query = "?"
|
46
61
|
unless params[:n].nil? || params[:n] == ""
|
47
62
|
query = "#{query}n=#{params[:n]}"
|
@@ -50,51 +65,54 @@ module Proxy
|
|
50
65
|
query = "#{query}last=#{params[:last]}" unless params[:last].nil? || params[:last] == ""
|
51
66
|
|
52
67
|
uri = URI.parse(
|
53
|
-
"#{
|
68
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
|
54
69
|
)
|
55
|
-
pulp_registry_request(uri)
|
70
|
+
pulp_registry_request(uri, headers)
|
56
71
|
end
|
57
72
|
|
58
73
|
def v1_search(params = {})
|
59
74
|
if params[:n].nil? || params[:n] == ""
|
60
|
-
|
75
|
+
limit = 25
|
61
76
|
else
|
62
|
-
|
77
|
+
limit = params[:n].to_i
|
63
78
|
end
|
79
|
+
return [] unless limit.positive?
|
64
80
|
|
65
|
-
|
66
|
-
|
67
|
-
user = params[:user].nil? ? nil : User.find(name: params[:user])
|
68
|
-
Proxy::ContainerGateway.catalog(user).each do |repo_name|
|
69
|
-
break if repo_count >= params[:n]
|
81
|
+
query = params[:q]
|
82
|
+
query = nil if query == ''
|
70
83
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
repositories
|
84
|
+
user = params[:user].nil? ? nil : database.connection[:users][{ name: params[:user] }]
|
85
|
+
|
86
|
+
repositories = query ? catalog(user).grep(:name, "%#{query}%") : catalog(user)
|
87
|
+
repositories.limit(limit).select_map(::Sequel[:repositories][:name])
|
77
88
|
end
|
78
89
|
|
79
90
|
def catalog(user = nil)
|
80
91
|
if user.nil?
|
81
92
|
unauthenticated_repos
|
82
93
|
else
|
83
|
-
|
94
|
+
database.connection[:repositories].
|
95
|
+
left_join(:repositories_users, repository_id: :id).
|
96
|
+
left_join(:users, ::Sequel[:users][:id] => :user_id).where(user_id: user[:id]).
|
97
|
+
or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
|
84
98
|
end
|
85
99
|
end
|
86
100
|
|
87
101
|
def unauthenticated_repos
|
88
|
-
|
102
|
+
database.connection[:repositories].where(auth_required: false).order(:name)
|
89
103
|
end
|
90
104
|
|
91
105
|
# Replaces the entire list of repositories
|
92
106
|
def update_repository_list(repo_list)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
107
|
+
# repositories_users cascades on deleting repositories (or users)
|
108
|
+
database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
|
109
|
+
repository = database.connection[:repositories]
|
110
|
+
repository.delete
|
111
|
+
|
112
|
+
repository.import(
|
113
|
+
%i[name auth_required],
|
114
|
+
repo_list.map { |repo| [repo['repository'], repo['auth_required'].to_s.downcase == "true"] }
|
115
|
+
)
|
98
116
|
end
|
99
117
|
end
|
100
118
|
|
@@ -103,122 +121,95 @@ module Proxy
|
|
103
121
|
# Get hash map of all users and their repositories
|
104
122
|
# Ex: {"users"=> [{"admin"=>[{"repository"=>"repo", "auth_required"=>"true"}]}]}
|
105
123
|
# Go through list of repositories and add them to the DB
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
found_repo = Repository.find(name: repo['repository'],
|
113
|
-
auth_required: repo['auth_required'].to_s.downcase == "true")
|
114
|
-
if found_repo.nil?
|
115
|
-
logger.warn("#{repo['repository']} does not exist in this smart proxy's environments")
|
116
|
-
elsif found_repo.auth_required
|
117
|
-
found_repo.add_user(User.find(name: user))
|
118
|
-
end
|
124
|
+
repositories = database.connection[:repositories]
|
125
|
+
|
126
|
+
entries = user_repo_maps['users'].flat_map do |user_repo_map|
|
127
|
+
user_repo_map.filter_map do |username, repos|
|
128
|
+
user_repo_names = repos.filter { |repo| repo['auth_required'].to_s.downcase == "true" }.map do |repo|
|
129
|
+
repo['repository']
|
119
130
|
end
|
131
|
+
user = database.connection[:users][{ name: username }]
|
132
|
+
repositories.where(name: user_repo_names, auth_required: true).select(:id).map { |repo| [repo[:id], user[:id]] }
|
120
133
|
end
|
121
134
|
end
|
135
|
+
entries.flatten!(1)
|
136
|
+
|
137
|
+
repositories_users = database.connection[:repositories_users]
|
138
|
+
database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
|
139
|
+
repositories_users.delete
|
140
|
+
repositories_users.import(%i[repository_id user_id], entries)
|
141
|
+
end
|
122
142
|
end
|
123
143
|
|
124
144
|
# Replaces the user-repo mapping for a single user
|
125
145
|
def update_user_repositories(username, repositories)
|
126
|
-
user =
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
146
|
+
user = database.connection[:users][{ name: username }]
|
147
|
+
|
148
|
+
user_repositories = database.connection[:repositories_users]
|
149
|
+
database.connection.transaction(isolation: :serializable,
|
150
|
+
retry_on: [Sequel::SerializationFailure],
|
151
|
+
num_retries: 10) do
|
152
|
+
user_repositories.where(user_id: user[:id]).delete
|
153
|
+
|
154
|
+
user_repositories.import(
|
155
|
+
%i[repository_id user_id],
|
156
|
+
database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
|
157
|
+
[repo[:id], user[:id]]
|
158
|
+
end
|
159
|
+
)
|
135
160
|
end
|
136
161
|
end
|
137
162
|
|
138
163
|
def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
|
139
|
-
repository =
|
164
|
+
repository = database.connection[:repositories][{ name: repo_name }]
|
140
165
|
|
141
166
|
# Repository doesn't exist
|
142
167
|
return false if repository.nil?
|
143
168
|
|
144
169
|
# Repository doesn't require auth
|
145
|
-
return true unless repository
|
170
|
+
return true unless repository[:auth_required]
|
146
171
|
|
147
|
-
if username && user_token_is_valid
|
172
|
+
if username && user_token_is_valid
|
148
173
|
# User is logged in and has access to the repository
|
149
|
-
|
150
|
-
|
174
|
+
return !database.connection[:repositories_users].where(
|
175
|
+
repository_id: repository[:id], user_id: database.connection[:users].first(name: username)[:id]
|
176
|
+
).empty?
|
151
177
|
end
|
152
178
|
|
153
179
|
false
|
154
180
|
end
|
155
181
|
|
156
182
|
def token_user(token)
|
157
|
-
|
183
|
+
database.connection[:users][{
|
184
|
+
id: database.connection[:authentication_tokens].where(token_checksum: checksum(token)).select(:user_id)
|
185
|
+
}]
|
158
186
|
end
|
159
187
|
|
160
188
|
def valid_token?(token)
|
161
|
-
|
189
|
+
!database.connection[:authentication_tokens].where(token_checksum: checksum(token)).where do
|
162
190
|
expire_at > Sequel::CURRENT_TIMESTAMP
|
163
|
-
end.
|
191
|
+
end.empty?
|
164
192
|
end
|
165
193
|
|
166
194
|
def insert_token(username, token, expire_at_string, clear_expired_tokens: true)
|
167
195
|
checksum = Digest::SHA256.hexdigest(token)
|
168
|
-
user =
|
169
|
-
|
170
|
-
AuthenticationToken.where(:token_checksum => checksum).delete
|
171
|
-
AuthenticationToken.create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
|
172
|
-
AuthenticationToken.where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete if clear_expired_tokens
|
173
|
-
end
|
174
|
-
|
175
|
-
def initialize_db
|
176
|
-
file_path = Proxy::ContainerGateway::Plugin.settings.sqlite_db_path
|
177
|
-
sqlite_timeout = Proxy::ContainerGateway::Plugin.settings.sqlite_timeout
|
178
|
-
conn = Sequel.connect("sqlite://#{file_path}", timeout: sqlite_timeout)
|
179
|
-
container_gateway_path = $LOAD_PATH.detect { |path| path.include? 'smart_proxy_container_gateway' }
|
180
|
-
begin
|
181
|
-
Sequel::Migrator.check_current(conn, "#{container_gateway_path}/smart_proxy_container_gateway/sequel_migrations")
|
182
|
-
rescue Sequel::Migrator::NotCurrentError
|
183
|
-
migrate_db(conn, container_gateway_path)
|
184
|
-
end
|
185
|
-
conn
|
186
|
-
end
|
196
|
+
user = Sequel::Model(database.connection[:users]).find_or_create(name: username)
|
187
197
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
198
|
+
database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
|
199
|
+
database.connection[:authentication_tokens].where(:token_checksum => checksum).delete
|
200
|
+
Sequel::Model(database.connection[:authentication_tokens]).
|
201
|
+
create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
|
202
|
+
return unless clear_expired_tokens
|
193
203
|
|
194
|
-
|
195
|
-
|
204
|
+
database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
|
205
|
+
end
|
196
206
|
end
|
197
207
|
|
198
|
-
|
199
|
-
OpenSSL::X509::Certificate.new(File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_cert))
|
200
|
-
end
|
208
|
+
private
|
201
209
|
|
202
|
-
def
|
203
|
-
|
204
|
-
File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_key)
|
205
|
-
)
|
210
|
+
def checksum(token)
|
211
|
+
Digest::SHA256.hexdigest(token)
|
206
212
|
end
|
207
213
|
end
|
208
|
-
|
209
|
-
class Repository < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories])
|
210
|
-
many_to_many :users
|
211
|
-
end
|
212
|
-
|
213
|
-
class User < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:users])
|
214
|
-
many_to_many :repositories
|
215
|
-
one_to_many :authentication_tokens
|
216
|
-
end
|
217
|
-
|
218
|
-
class RepositoryUser < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories_users]); end
|
219
|
-
|
220
|
-
class AuthenticationToken < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:authentication_tokens])
|
221
|
-
many_to_one :users
|
222
|
-
end
|
223
214
|
end
|
224
215
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
module Proxy
|
3
|
+
module ContainerGateway
|
4
|
+
class Database
|
5
|
+
attr_reader :connection
|
6
|
+
|
7
|
+
def initialize(connection_string, prior_sqlite_db_path = nil)
|
8
|
+
@connection = Sequel.connect(connection_string)
|
9
|
+
if connection_string.start_with?('sqlite://')
|
10
|
+
@connection.run("PRAGMA foreign_keys = ON;")
|
11
|
+
@connection.run("PRAGMA journal_mode = wal;")
|
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)
|
16
|
+
end
|
17
|
+
migrate
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def migrate
|
23
|
+
Sequel.extension :migration, :core_extensions
|
24
|
+
migration_path = File.join(__dir__, 'sequel_migrations')
|
25
|
+
begin
|
26
|
+
Sequel::Migrator.check_current(@connection, migration_path)
|
27
|
+
rescue Sequel::Migrator::NotCurrentError
|
28
|
+
Sequel::Migrator.run(@connection, migration_path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def migrate_to_postgres(sqlite_db, postgres_db)
|
33
|
+
migrate
|
34
|
+
sqlite_db.transaction do
|
35
|
+
sqlite_db.tables.each do |table|
|
36
|
+
next if table == :schema_info
|
37
|
+
|
38
|
+
sqlite_db[table].each do |row|
|
39
|
+
postgres_db[table.to_sym].insert(row)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Proxy
|
2
|
+
module ContainerGateway
|
3
|
+
module DependencyInjection
|
4
|
+
include Proxy::DependencyInjection::Accessors
|
5
|
+
def container_instance
|
6
|
+
@container_instance ||= ::Proxy::Plugins.instance.find { |p| p[:name] == :container_gateway }[:di_container]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -25,8 +25,8 @@ Sequel.migration do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# Populate the new user_id foreign key for all authentication_tokens
|
28
|
-
from(:authentication_tokens).
|
29
|
-
|
28
|
+
from(:authentication_tokens).
|
29
|
+
update(user_id: from(:users).select(:id).where(name: Sequel[:authentication_tokens][:username]))
|
30
30
|
|
31
31
|
alter_table(:authentication_tokens) do
|
32
32
|
drop_column :username
|
data/lib/smart_proxy_container_gateway/sequel_migrations/004_users_repositories_cascade_delete.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
up do
|
3
|
+
# SQLite does not support dropping columns until version 3.35, which is not in the EL8 ecosystem as of March, 2024.
|
4
|
+
create_table(:repositories_users2) do
|
5
|
+
foreign_key :repository_id, :repositories, on_delete: :cascade
|
6
|
+
foreign_key :user_id, :users, on_delete: :cascade
|
7
|
+
primary_key %i[repository_id user_id]
|
8
|
+
index %i[repository_id user_id]
|
9
|
+
end
|
10
|
+
run "INSERT INTO repositories_users2(repository_id, user_id) SELECT repository_id, user_id from repositories_users"
|
11
|
+
|
12
|
+
drop_table(:repositories_users)
|
13
|
+
rename_table(:repositories_users2, :repositories_users)
|
14
|
+
end
|
15
|
+
|
16
|
+
down do
|
17
|
+
create_table(:repositories_users2) do
|
18
|
+
foreign_key :repository_id, :repositories
|
19
|
+
foreign_key :user_id, :users
|
20
|
+
primary_key %i[repository_id user_id]
|
21
|
+
index %i[repository_id user_id]
|
22
|
+
end
|
23
|
+
run "INSERT INTO repositories_users2(repository_id, user_id) SELECT repository_id, user_id from repositories_users"
|
24
|
+
|
25
|
+
drop_table(:repositories_users)
|
26
|
+
rename_table(:repositories_users2, :repositories_users)
|
27
|
+
end
|
28
|
+
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.0.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-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: sequel
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,10 +82,13 @@ files:
|
|
68
82
|
- lib/smart_proxy_container_gateway/container_gateway_api.rb
|
69
83
|
- lib/smart_proxy_container_gateway/container_gateway_http_config.ru
|
70
84
|
- lib/smart_proxy_container_gateway/container_gateway_main.rb
|
85
|
+
- lib/smart_proxy_container_gateway/database.rb
|
86
|
+
- lib/smart_proxy_container_gateway/dependency_injection.rb
|
71
87
|
- lib/smart_proxy_container_gateway/foreman_api.rb
|
72
88
|
- lib/smart_proxy_container_gateway/sequel_migrations/001_initial.rb
|
73
89
|
- lib/smart_proxy_container_gateway/sequel_migrations/002_auth_tokens.rb
|
74
90
|
- lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb
|
91
|
+
- lib/smart_proxy_container_gateway/sequel_migrations/004_users_repositories_cascade_delete.rb
|
75
92
|
- lib/smart_proxy_container_gateway/version.rb
|
76
93
|
- settings.d/container_gateway.yml.example
|
77
94
|
homepage: https://github.com/Katello/smart_proxy_container_gateway
|
@@ -93,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
110
|
- !ruby/object:Gem::Version
|
94
111
|
version: '0'
|
95
112
|
requirements: []
|
96
|
-
rubygems_version: 3.4.
|
113
|
+
rubygems_version: 3.4.21
|
97
114
|
signing_key:
|
98
115
|
specification_version: 4
|
99
116
|
summary: Pulp 3 container registry support for Foreman/Katello Smart-Proxy
|