smart_proxy_container_gateway 1.1.0 → 2.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 +14 -2
- data/lib/smart_proxy_container_gateway/container_gateway.rb +16 -0
- data/lib/smart_proxy_container_gateway/container_gateway_api.rb +31 -21
- data/lib/smart_proxy_container_gateway/container_gateway_main.rb +93 -107
- data/lib/smart_proxy_container_gateway/database.rb +53 -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
- metadata +21 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fee65676d4362e1328671832a7bf3308b193ab1265ea5626eb52d5300ab7d35a
|
4
|
+
data.tar.gz: fe57f17f732079b2ac27ce70cc7c04ef3ed141283cba58db75676a36dfb4b678
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6f0716504c021e799a8a7d31390c9d203c248530073211cf24d7659439b1220647306033c4e60bf98b9425d427b822a8f98b6962f3dab788883bc17b3f5e9a8
|
7
|
+
data.tar.gz: cfe03eaf3ce5e9ffa23dd355849e81abf249cede80a0f98ac12cf056fc7ec20de81195794e820c44adc9eea9000f176c837b70756670244334b1bbca4690dc54
|
data/README.md
CHANGED
@@ -35,11 +35,23 @@ 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 (default port is 5432)
|
42
|
+
:database_backend: postgresql
|
43
|
+
:postgresql_connection_string: postgres://foreman-proxy:changeme@localhost:5432/container_gateway
|
44
|
+
```
|
45
|
+
|
46
|
+
When switching from SQLite to PostgreSQL, if the PostgreSQL database is empty, the SQLite database will be automatically migrated to PostgreSQL.
|
47
|
+
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.
|
48
|
+
The SQLite database file will be deleted after the migration to PostgreSQL is complete.
|
49
|
+
|
50
|
+
Database migrations are completely automated. The plugin checks if the database is up-to-date at initialization time.
|
39
51
|
|
40
52
|
# Katello interaction
|
41
53
|
|
42
|
-
Auth information is retrieved from the Katello server during smart proxy sync time and cached in the
|
54
|
+
Auth information is retrieved from the Katello server during smart proxy sync time and cached in the database.
|
43
55
|
|
44
56
|
Logging in with a container client will cause the Container Gateway to fetch a token from Katello using the login information.
|
45
57
|
|
@@ -7,6 +7,7 @@ module Proxy
|
|
7
7
|
|
8
8
|
default_settings :pulp_endpoint => "https://#{`hostname`.strip}",
|
9
9
|
:katello_registry_path => '/v2/',
|
10
|
+
:database_backend => 'sqlite',
|
10
11
|
:sqlite_db_path => '/var/lib/foreman-proxy/smart_proxy_container_gateway.db',
|
11
12
|
:sqlite_timeout => 30_000
|
12
13
|
|
@@ -25,6 +26,21 @@ module Proxy
|
|
25
26
|
validate :pulp_endpoint, url: true
|
26
27
|
|
27
28
|
rackup_path File.join(__dir__, 'container_gateway_http_config.ru')
|
29
|
+
|
30
|
+
load_dependency_injection_wirings do |container_instance, settings|
|
31
|
+
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
|
+
)
|
36
|
+
end)
|
37
|
+
container_instance.singleton_dependency :container_gateway_main_impl, (lambda do
|
38
|
+
Proxy::ContainerGateway::ContainerGatewayMain.new(
|
39
|
+
database: container_instance.get_dependency(:database_impl),
|
40
|
+
**settings.slice(:pulp_endpoint, :pulp_client_ssl_ca, :pulp_client_ssl_cert, :pulp_client_ssl_key)
|
41
|
+
)
|
42
|
+
end)
|
43
|
+
end
|
28
44
|
end
|
29
45
|
end
|
30
46
|
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,19 @@ 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
|
+
container_gateway_main.ping
|
20
23
|
end
|
21
24
|
|
22
25
|
get '/v2/?' do
|
23
26
|
if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
|
24
27
|
response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
|
25
|
-
|
28
|
+
container_gateway_main.ping
|
26
29
|
else
|
27
30
|
redirect_authorization_headers
|
28
31
|
halt 401, "unauthorized"
|
@@ -33,7 +36,7 @@ module Proxy
|
|
33
36
|
repository = params[:splat][0]
|
34
37
|
tag = params[:splat][1]
|
35
38
|
handle_repo_auth(repository, auth_header, request)
|
36
|
-
redirection_location =
|
39
|
+
redirection_location = container_gateway_main.manifests(repository, tag)
|
37
40
|
redirect to(redirection_location)
|
38
41
|
end
|
39
42
|
|
@@ -41,14 +44,14 @@ module Proxy
|
|
41
44
|
repository = params[:splat][0]
|
42
45
|
digest = params[:splat][1]
|
43
46
|
handle_repo_auth(repository, auth_header, request)
|
44
|
-
redirection_location =
|
47
|
+
redirection_location = container_gateway_main.blobs(repository, digest)
|
45
48
|
redirect to(redirection_location)
|
46
49
|
end
|
47
50
|
|
48
51
|
get '/v2/*/tags/list/?' do
|
49
52
|
repository = params[:splat][0]
|
50
53
|
handle_repo_auth(repository, auth_header, request)
|
51
|
-
pulp_response =
|
54
|
+
pulp_response = container_gateway_main.tags(repository, params)
|
52
55
|
# "link"=>["<http://pulpcore-api/v2/container-image-name/tags/list?n=100&last=last-tag-name>; rel=\"next\""],
|
53
56
|
# https://docs.docker.com/registry/spec/api/#pagination-1
|
54
57
|
if pulp_response['link'].nil?
|
@@ -74,19 +77,23 @@ module Proxy
|
|
74
77
|
end
|
75
78
|
params[:user] = username
|
76
79
|
end
|
77
|
-
repositories =
|
80
|
+
repositories = container_gateway_main.v1_search(params)
|
78
81
|
|
79
82
|
content_type :json
|
80
|
-
{
|
83
|
+
{
|
84
|
+
num_results: repositories.size,
|
85
|
+
query: params[:q],
|
86
|
+
results: repositories.map { |repo_name| { description: '', name: repo_name } }
|
87
|
+
}.to_json
|
81
88
|
end
|
82
89
|
|
83
90
|
get '/v2/_catalog/?' do
|
84
91
|
catalog = []
|
85
92
|
if auth_header.present?
|
86
93
|
if auth_header.unauthorized_token?
|
87
|
-
catalog =
|
94
|
+
catalog = container_gateway_main.catalog.select_map(::Sequel[:repositories][:name])
|
88
95
|
elsif auth_header.valid_user_token?
|
89
|
-
catalog =
|
96
|
+
catalog = container_gateway_main.catalog(auth_header.user).select_map(::Sequel[:repositories][:name])
|
90
97
|
else
|
91
98
|
redirect_authorization_headers
|
92
99
|
halt 401, "unauthorized"
|
@@ -131,7 +138,7 @@ module Proxy
|
|
131
138
|
expires_in = token_response_body.fetch("expires_in", 60)
|
132
139
|
expires_at = token_issue_time + expires_in.seconds
|
133
140
|
|
134
|
-
|
141
|
+
container_gateway_main.insert_token(
|
135
142
|
request.params['account'],
|
136
143
|
token_response_body['token'],
|
137
144
|
expires_at.rfc3339
|
@@ -141,8 +148,8 @@ module Proxy
|
|
141
148
|
if repo_response.code.to_i != 200
|
142
149
|
halt repo_response.code.to_i, repo_response.body
|
143
150
|
else
|
144
|
-
|
145
|
-
|
151
|
+
container_gateway_main.update_user_repositories(request.params['account'],
|
152
|
+
JSON.parse(repo_response.body)['repositories'])
|
146
153
|
end
|
147
154
|
|
148
155
|
# Return the original token response from Katello
|
@@ -154,13 +161,13 @@ module Proxy
|
|
154
161
|
do_authorize_any
|
155
162
|
|
156
163
|
content_type :json
|
157
|
-
{ users:
|
164
|
+
{ users: database.connection[:users].map(:name) }.to_json
|
158
165
|
end
|
159
166
|
|
160
167
|
put '/user_repository_mapping/?' do
|
161
168
|
do_authorize_any
|
162
169
|
|
163
|
-
|
170
|
+
container_gateway_main.update_user_repo_mapping(params)
|
164
171
|
{}
|
165
172
|
end
|
166
173
|
|
@@ -168,7 +175,7 @@ module Proxy
|
|
168
175
|
do_authorize_any
|
169
176
|
|
170
177
|
repositories = params['repositories'].nil? ? [] : params['repositories']
|
171
|
-
|
178
|
+
container_gateway_main.update_repository_list(repositories)
|
172
179
|
{}
|
173
180
|
end
|
174
181
|
|
@@ -176,14 +183,13 @@ module Proxy
|
|
176
183
|
|
177
184
|
def handle_repo_auth(repository, auth_header, request)
|
178
185
|
user_token_is_valid = false
|
179
|
-
# FIXME: Getting unauthenticated token here...
|
180
186
|
if auth_header.present? && auth_header.valid_user_token?
|
181
187
|
user_token_is_valid = true
|
182
|
-
username = auth_header.user
|
188
|
+
username = auth_header.user[:name]
|
183
189
|
end
|
184
190
|
username = request.params['account'] if username.nil?
|
185
191
|
|
186
|
-
return if
|
192
|
+
return if container_gateway_main.authorized_for_repo?(repository, user_token_is_valid, username)
|
187
193
|
|
188
194
|
redirect_authorization_headers
|
189
195
|
halt 401, "unauthorized"
|
@@ -201,6 +207,10 @@ module Proxy
|
|
201
207
|
end
|
202
208
|
|
203
209
|
class AuthorizationHeader
|
210
|
+
extend ::Proxy::ContainerGateway::DependencyInjection
|
211
|
+
|
212
|
+
inject_attr :database_impl, :database
|
213
|
+
inject_attr :container_gateway_main_impl, :container_gateway_main
|
204
214
|
UNAUTHORIZED_TOKEN = 'unauthorized'.freeze
|
205
215
|
|
206
216
|
def initialize(value)
|
@@ -208,11 +218,11 @@ module Proxy
|
|
208
218
|
end
|
209
219
|
|
210
220
|
def user
|
211
|
-
|
221
|
+
container_gateway_main.token_user(@value.split(' ')[1])
|
212
222
|
end
|
213
223
|
|
214
224
|
def valid_user_token?
|
215
|
-
token_auth? &&
|
225
|
+
token_auth? && container_gateway_main.valid_token?(@value.split(' ')[1])
|
216
226
|
end
|
217
227
|
|
218
228
|
def raw_header
|
@@ -1,19 +1,31 @@
|
|
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
|
-
|
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
|
+
|
12
24
|
def pulp_registry_request(uri)
|
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|
|
@@ -23,20 +35,20 @@ module Proxy
|
|
23
35
|
end
|
24
36
|
|
25
37
|
def ping
|
26
|
-
uri = URI.parse("#{
|
38
|
+
uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
|
27
39
|
pulp_registry_request(uri).body
|
28
40
|
end
|
29
41
|
|
30
42
|
def manifests(repository, tag)
|
31
43
|
uri = URI.parse(
|
32
|
-
"#{
|
44
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
|
33
45
|
)
|
34
46
|
pulp_registry_request(uri)['location']
|
35
47
|
end
|
36
48
|
|
37
49
|
def blobs(repository, digest)
|
38
50
|
uri = URI.parse(
|
39
|
-
"#{
|
51
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
|
40
52
|
)
|
41
53
|
pulp_registry_request(uri)['location']
|
42
54
|
end
|
@@ -50,51 +62,54 @@ module Proxy
|
|
50
62
|
query = "#{query}last=#{params[:last]}" unless params[:last].nil? || params[:last] == ""
|
51
63
|
|
52
64
|
uri = URI.parse(
|
53
|
-
"#{
|
65
|
+
"#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
|
54
66
|
)
|
55
67
|
pulp_registry_request(uri)
|
56
68
|
end
|
57
69
|
|
58
70
|
def v1_search(params = {})
|
59
71
|
if params[:n].nil? || params[:n] == ""
|
60
|
-
|
72
|
+
limit = 25
|
61
73
|
else
|
62
|
-
|
74
|
+
limit = params[:n].to_i
|
63
75
|
end
|
76
|
+
return [] unless limit.positive?
|
64
77
|
|
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]
|
78
|
+
query = params[:q]
|
79
|
+
query = nil if query == ''
|
70
80
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
repositories
|
81
|
+
user = params[:user].nil? ? nil : database.connection[:users][{ name: params[:user] }]
|
82
|
+
|
83
|
+
repositories = query ? catalog(user).grep(:name, "%#{query}%") : catalog(user)
|
84
|
+
repositories.limit(limit).select_map(::Sequel[:repositories][:name])
|
77
85
|
end
|
78
86
|
|
79
87
|
def catalog(user = nil)
|
80
88
|
if user.nil?
|
81
89
|
unauthenticated_repos
|
82
90
|
else
|
83
|
-
|
91
|
+
database.connection[:repositories].
|
92
|
+
left_join(:repositories_users, repository_id: :id).
|
93
|
+
left_join(:users, ::Sequel[:users][:id] => :user_id).where(user_id: user[:id]).
|
94
|
+
or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
87
98
|
def unauthenticated_repos
|
88
|
-
|
99
|
+
database.connection[:repositories].where(auth_required: false).order(:name)
|
89
100
|
end
|
90
101
|
|
91
102
|
# Replaces the entire list of repositories
|
92
103
|
def update_repository_list(repo_list)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
104
|
+
# repositories_users cascades on deleting repositories (or users)
|
105
|
+
database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
|
106
|
+
repository = database.connection[:repositories]
|
107
|
+
repository.delete
|
108
|
+
|
109
|
+
repository.import(
|
110
|
+
%i[name auth_required],
|
111
|
+
repo_list.map { |repo| [repo['repository'], repo['auth_required'].to_s.downcase == "true"] }
|
112
|
+
)
|
98
113
|
end
|
99
114
|
end
|
100
115
|
|
@@ -103,122 +118,93 @@ module Proxy
|
|
103
118
|
# Get hash map of all users and their repositories
|
104
119
|
# Ex: {"users"=> [{"admin"=>[{"repository"=>"repo", "auth_required"=>"true"}]}]}
|
105
120
|
# 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
|
121
|
+
repositories = database.connection[:repositories]
|
122
|
+
|
123
|
+
entries = user_repo_maps['users'].flat_map do |user_repo_map|
|
124
|
+
user_repo_map.filter_map do |username, repos|
|
125
|
+
user_repo_names = repos.filter { |repo| repo['auth_required'].to_s.downcase == "true" }.map do |repo|
|
126
|
+
repo['repository']
|
119
127
|
end
|
128
|
+
user = database.connection[:users][{ name: username }]
|
129
|
+
repositories.where(name: user_repo_names, auth_required: true).select(:id).map { |repo| [repo[:id], user[:id]] }
|
120
130
|
end
|
121
131
|
end
|
132
|
+
entries.flatten!(1)
|
133
|
+
|
134
|
+
repositories_users = database.connection[:repositories_users]
|
135
|
+
database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
|
136
|
+
repositories_users.delete
|
137
|
+
repositories_users.import(%i[repository_id user_id], entries)
|
138
|
+
end
|
122
139
|
end
|
123
140
|
|
124
141
|
# Replaces the user-repo mapping for a single user
|
125
142
|
def update_user_repositories(username, repositories)
|
126
|
-
user =
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
143
|
+
user = database.connection[:users][{ name: username }]
|
144
|
+
|
145
|
+
user_repositories = database.connection[:repositories_users]
|
146
|
+
database.connection.transaction(isolation: :serializable,
|
147
|
+
retry_on: [Sequel::SerializationFailure],
|
148
|
+
num_retries: 10) do
|
149
|
+
user_repositories.where(user_id: user[:id]).delete
|
150
|
+
|
151
|
+
user_repositories.import(
|
152
|
+
%i[repository_id user_id],
|
153
|
+
database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
|
154
|
+
[repo[:id], user[:id]]
|
155
|
+
end
|
156
|
+
)
|
135
157
|
end
|
136
158
|
end
|
137
159
|
|
138
160
|
def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
|
139
|
-
repository =
|
161
|
+
repository = database.connection[:repositories][{ name: repo_name }]
|
140
162
|
|
141
163
|
# Repository doesn't exist
|
142
164
|
return false if repository.nil?
|
143
165
|
|
144
166
|
# Repository doesn't require auth
|
145
|
-
return true unless repository
|
167
|
+
return true unless repository[:auth_required]
|
146
168
|
|
147
|
-
if username && user_token_is_valid
|
169
|
+
if username && user_token_is_valid
|
148
170
|
# User is logged in and has access to the repository
|
149
|
-
|
150
|
-
|
171
|
+
return !database.connection[:repositories_users].where(
|
172
|
+
repository_id: repository[:id], user_id: database.connection[:users].first(name: username)[:id]
|
173
|
+
).empty?
|
151
174
|
end
|
152
175
|
|
153
176
|
false
|
154
177
|
end
|
155
178
|
|
156
179
|
def token_user(token)
|
157
|
-
|
180
|
+
database.connection[:users][{
|
181
|
+
id: database.connection[:authentication_tokens].where(token_checksum: checksum(token)).select(:user_id)
|
182
|
+
}]
|
158
183
|
end
|
159
184
|
|
160
185
|
def valid_token?(token)
|
161
|
-
|
186
|
+
!database.connection[:authentication_tokens].where(token_checksum: checksum(token)).where do
|
162
187
|
expire_at > Sequel::CURRENT_TIMESTAMP
|
163
|
-
end.
|
188
|
+
end.empty?
|
164
189
|
end
|
165
190
|
|
166
191
|
def insert_token(username, token, expire_at_string, clear_expired_tokens: true)
|
167
192
|
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
|
193
|
+
user = Sequel::Model(database.connection[:users]).find_or_create(name: username)
|
187
194
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
end
|
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
|
193
199
|
|
194
|
-
|
195
|
-
Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_ca
|
200
|
+
database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
|
196
201
|
end
|
197
202
|
|
198
|
-
|
199
|
-
OpenSSL::X509::Certificate.new(File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_cert))
|
200
|
-
end
|
203
|
+
private
|
201
204
|
|
202
|
-
def
|
203
|
-
|
204
|
-
File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_key)
|
205
|
-
)
|
205
|
+
def checksum(token)
|
206
|
+
Digest::SHA256.hexdigest(token)
|
206
207
|
end
|
207
208
|
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
209
|
end
|
224
210
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
module Proxy
|
3
|
+
module ContainerGateway
|
4
|
+
class Database
|
5
|
+
attr_reader :connection
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
if options[:database_backend] == 'sqlite'
|
9
|
+
@connection = Sequel.connect("sqlite://#{options[:sqlite_db_path]}", timeout: options[:sqlite_timeout])
|
10
|
+
@connection.run("PRAGMA foreign_keys = ON;")
|
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
|
23
|
+
end
|
24
|
+
migrate
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def migrate
|
30
|
+
Sequel.extension :migration, :core_extensions
|
31
|
+
migration_path = File.join(__dir__, 'sequel_migrations')
|
32
|
+
begin
|
33
|
+
Sequel::Migrator.check_current(@connection, migration_path)
|
34
|
+
rescue Sequel::Migrator::NotCurrentError
|
35
|
+
Sequel::Migrator.run(@connection, migration_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def migrate_to_postgres(sqlite_db, postgres_db)
|
40
|
+
migrate
|
41
|
+
sqlite_db.transaction do
|
42
|
+
sqlite_db.tables.each do |table|
|
43
|
+
next if table == :schema_info
|
44
|
+
|
45
|
+
sqlite_db[table].each do |row|
|
46
|
+
postgres_db[table.to_sym].insert(row)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
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
|
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: 2.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:
|
11
|
+
date: 2024-04-15 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
|
@@ -84,7 +101,7 @@ require_paths:
|
|
84
101
|
- lib
|
85
102
|
required_ruby_version: !ruby/object:Gem::Requirement
|
86
103
|
requirements:
|
87
|
-
- - "
|
104
|
+
- - ">="
|
88
105
|
- !ruby/object:Gem::Version
|
89
106
|
version: '2.7'
|
90
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -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
|