smart_proxy_container_gateway 1.2.0 → 3.0.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 +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
|