Class: Proxy::ContainerGateway::ContainerGatewayMain

Inherits:
Object
  • Object
show all
Defined in:
lib/smart_proxy_container_gateway/container_gateway_main.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(database:, pulp_endpoint:, pulp_client_ssl_ca:, pulp_client_ssl_cert:, pulp_client_ssl_key:, client_endpoint: nil) ⇒ ContainerGatewayMain

rubocop:disable Metrics/ParameterLists, Layout/LineLength



16
17
18
19
20
21
22
23
24
25
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 16

def initialize(database:, pulp_endpoint:, pulp_client_ssl_ca:, pulp_client_ssl_cert:, pulp_client_ssl_key:, client_endpoint: nil)
  @database = database
  @pulp_endpoint = pulp_endpoint
  @client_endpoint = client_endpoint || pulp_endpoint
  @pulp_client_ssl_ca = pulp_client_ssl_ca
  @pulp_client_ssl_cert = OpenSSL::X509::Certificate.new(File.read(pulp_client_ssl_cert))
  @pulp_client_ssl_key = OpenSSL::PKey::RSA.new(
    File.read(pulp_client_ssl_key)
  )
end

Instance Attribute Details

#client_endpointObject (readonly)

Returns the value of attribute client_endpoint.



13
14
15
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 13

def client_endpoint
  @client_endpoint
end

#databaseObject (readonly)

Returns the value of attribute database.



13
14
15
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 13

def database
  @database
end

Instance Method Details

#authorized_for_repo?(repo_name, user_token_is_valid, username = nil) ⇒ Boolean

Returns: true if the user is authorized to access the repo, or false if the user is not authorized to access the repo or if it does not exist



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 266

def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
  repository = database.connection[:repositories][{ name: repo_name }]

  # Repository doesn't exist
  return false if repository.nil?

  # Repository doesn't require auth
  return true unless repository[:auth_required]

  if username && user_token_is_valid
    # User is logged in and has access to the repository
    return !database.connection[:repositories_users].where(
      repository_id: repository[:id], user_id: database.connection[:users].first(name: username)[:id]
    ).empty?
  end

  false
end

#blobs(repository, digest, headers) ⇒ Object



64
65
66
67
68
69
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 64

def blobs(repository, digest, headers)
  uri = URI.parse(
    "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/blobs/#{digest}"
  )
  pulp_registry_request(uri, headers)
end

#build_host_entries(hosts, repositories, host_uuid, repos) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 216

def build_host_entries(hosts, repositories, host_uuid, repos)
  return if host_uuid.nil? || host_uuid.to_s.strip.empty?

  host = hosts[{ uuid: host_uuid }]
  return unless host
  return if repos.nil? || repos.empty?

  repo_names = extract_auth_required_repo_names(repos)
  repositories
    .where(name: repo_names, auth_required: true)
    .select(:id)
    .map { |repo| [repo[:id], host[:id]] }
end

#build_host_repository_mapping(host_repo_maps) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 202

def build_host_repository_mapping(host_repo_maps)
  return [] if host_repo_maps['hosts'].nil?

  hosts = database.connection[:hosts]
  repositories = database.connection[:repositories]

  entries = host_repo_maps['hosts'].flat_map do |host_map|
    host_map.filter_map do |host_uuid, repos|
      build_host_entries(hosts, repositories, host_uuid, repos)
    end
  end
  entries&.flatten(1)&.compact
end

#catalog(user = nil) ⇒ Object



102
103
104
105
106
107
108
109
110
111
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 102

def catalog(user = nil)
  if user.nil?
    unauthenticated_repos
  else
    database.connection[:repositories].
      left_join(:repositories_users, repository_id: :id).
      left_join(:users, ::Sequel[:users][:id] => :user_id).where(user_id: user[:id]).
      or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
  end
end

#cert_authorized_for_repo?(repo_name, uuid) ⇒ Boolean



285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 285

def cert_authorized_for_repo?(repo_name, uuid)
  database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
    repository = database.connection[:repositories][{ name: repo_name }]
    return false if repository.nil?
    return true unless repository[:auth_required]

    database.connection[:hosts_repositories]
            .where(repository_id: repository[:id])
            .join(:hosts, id: :host_id)
            .where(Sequel[:hosts][:uuid] => uuid)
            .any?
  end
end

#extract_auth_required_repo_names(repos) ⇒ Object



230
231
232
233
234
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 230

def extract_auth_required_repo_names(repos)
  repos
    .select { |repo| repo['auth_required'].to_s.downcase == "true" }
    .map { |repo| repo['repository'] }
end

#find_or_create_host(uuid) ⇒ Object



258
259
260
261
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 258

def find_or_create_host(uuid)
  database.connection[:hosts].insert_conflict(target: :uuid, action: :ignore).insert(uuid: uuid)
  database.connection[:hosts][{ uuid: uuid }]
end

#flatpak_static_index(headers, params = {}) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 44

def flatpak_static_index(headers, params = {})
  uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/index/static")
  unless params.empty?
    uri.query = params.map { |k, v| "#{ERB::Util.url_encode(k.to_s)}=#{ERB::Util.url_encode(v.to_s)}" }.join('&')
  end
  pulp_registry_request(uri, headers)
end

#host_catalog(host_uuid = nil) ⇒ Object



113
114
115
116
117
118
119
120
121
122
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 113

def host_catalog(host_uuid = nil)
  if host_uuid.nil?
    unauthenticated_repos
  else
    database.connection[:repositories].
      left_join(:hosts_repositories, repository_id: :id).
      left_join(:hosts, ::Sequel[:hosts][:id] => :host_id).where(uuid: host_uuid).
      or(Sequel[:repositories][:auth_required] => false).order(::Sequel[:repositories][:name])
  end
end

#insert_token(username, token, expire_at_string, clear_expired_tokens: true) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 311

def insert_token(username, token, expire_at_string, clear_expired_tokens: true)
  checksum = Digest::SHA256.hexdigest(token)
  user = Sequel::Model(database.connection[:users]).find_or_create(name: username)

  database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
    database.connection[:authentication_tokens].where(:token_checksum => checksum).delete
    Sequel::Model(database.connection[:authentication_tokens]).
      create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
    return unless clear_expired_tokens

    database.connection[:authentication_tokens].where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete
  end
end

#manifests(repository, tag, headers) ⇒ Object



57
58
59
60
61
62
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 57

def manifests(repository, tag, headers)
  uri = URI.parse(
    "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/manifests/#{tag}"
  )
  pulp_registry_request(uri, headers)
end

#ping(headers) ⇒ Object



52
53
54
55
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 52

def ping(headers)
  uri = URI.parse("#{@pulp_endpoint}/pulpcore_registry/v2/")
  pulp_registry_request(uri, headers)
end

#pulp_registry_request(uri, headers) ⇒ Object

rubocop:enable Metrics/ParameterLists, Layout/LineLength



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 28

def pulp_registry_request(uri, headers)
  http_client = Net::HTTP.new(uri.host, uri.port)
  http_client.ca_file = @pulp_client_ssl_ca
  http_client.cert = @pulp_client_ssl_cert
  http_client.key = @pulp_client_ssl_key
  http_client.use_ssl = true

  http_client.start do |http|
    request = Net::HTTP::Get.new uri
    headers.each do |key, value|
      request[key] = value
    end
    http.request request
  end
end

#tags(repository, headers, params = {}) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 71

def tags(repository, headers, params = {})
  query = "?"
  unless params[:n].nil? || params[:n] == ""
    query = "#{query}n=#{params[:n]}"
    query = "#{query}&" unless params[:last].nil?
  end
  query = "#{query}last=#{params[:last]}" unless params[:last].nil? || params[:last] == ""

  uri = URI.parse(
    "#{@pulp_endpoint}/pulpcore_registry/v2/#{repository}/tags/list#{query}"
  )
  pulp_registry_request(uri, headers)
end

#token_user(token) ⇒ Object



299
300
301
302
303
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 299

def token_user(token)
  database.connection[:users][{
    id: database.connection[:authentication_tokens].where(token_checksum: checksum(token)).select(:user_id)
  }]
end

#unauthenticated_reposObject



124
125
126
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 124

def unauthenticated_repos
  database.connection[:repositories].where(auth_required: false).order(:name)
end

#update_host_repo_mapping(host_repo_maps) ⇒ Object

Replaces the entire host-repo mapping for all hosts. Assumes host is present in the DB.



188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 188

def update_host_repo_mapping(host_repo_maps)
  # Get DB tables
  hosts_repositories = database.connection[:hosts_repositories]

  # Build list of [repository_id, host_id] pairs
  entries = build_host_repository_mapping(host_repo_maps)

  # Insert all in a single transaction
  database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
    hosts_repositories.delete
    hosts_repositories.import(i[repository_id host_id], entries) unless entries.nil? || entries.empty?
  end
end

#update_host_repositories(uuid, repositories) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 236

def update_host_repositories(uuid, repositories)
  return if uuid.nil? || uuid.to_s.strip.empty?

  host = find_or_create_host(uuid)
  return unless host

  hosts_repositories = database.connection[:hosts_repositories]
  database.connection.transaction(isolation: :serializable,
                                  retry_on: [Sequel::SerializationFailure],
                                  num_retries: 10) do
    hosts_repositories.where(host_id: host[:id]).delete
    return if repositories.nil? || repositories.empty?

    hosts_repositories.import(
      i[repository_id host_id],
      database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
        [repo[:id], host[:id]]
      end
    )
  end
end

#update_repository_list(repo_list) ⇒ Object

Replaces the entire list of repositories



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 129

def update_repository_list(repo_list)
  # repositories_users cascades on deleting repositories (or users)
  database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
    repository = database.connection[:repositories]
    repository.delete

    repository.import(
      i[name auth_required],
      repo_list.map { |repo| [repo['repository'], repo['auth_required'].to_s.downcase == "true"] }
    )
  end
end

#update_user_repo_mapping(user_repo_maps) ⇒ Object

Replaces the entire user-repo mapping for all logged-in users



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 143

def update_user_repo_mapping(user_repo_maps)
  # Get hash map of all users and their repositories
  # Ex: {"users"=> [{"admin"=>[{"repository"=>"repo", "auth_required"=>"true"}]}]}
  # Go through list of repositories and add them to the DB
  repositories = database.connection[:repositories]

  entries = user_repo_maps['users'].flat_map do |user_repo_map|
    user_repo_map.filter_map do |username, repos|
      user_repo_names = repos.filter { |repo| repo['auth_required'].to_s.downcase == "true" }.map do |repo|
        repo['repository']
      end
      user = database.connection[:users][{ name: username }]
      repositories.where(name: user_repo_names, auth_required: true).select(:id).map { |repo| [repo[:id], user[:id]] }
    end
  end
  entries.flatten!(1)

  repositories_users = database.connection[:repositories_users]
  database.connection.transaction(isolation: :serializable, retry_on: [Sequel::SerializationFailure]) do
    repositories_users.delete
    repositories_users.import(i[repository_id user_id], entries)
  end
end

#update_user_repositories(username, repositories) ⇒ Object

Replaces the user-repo mapping for a single user



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 168

def update_user_repositories(username, repositories)
  user = database.connection[:users][{ name: username }]

  user_repositories = database.connection[:repositories_users]
  database.connection.transaction(isolation: :serializable,
                                  retry_on: [Sequel::SerializationFailure],
                                  num_retries: 10) do
    user_repositories.where(user_id: user[:id]).delete

    user_repositories.import(
      i[repository_id user_id],
      database.connection[:repositories].where(name: repositories, auth_required: true).select(:id).map do |repo|
        [repo[:id], user[:id]]
      end
    )
  end
end

#v1_search(params = {}) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 85

def v1_search(params = {})
  if params[:n].nil? || params[:n] == ""
    limit = 25
  else
    limit = params[:n].to_i
  end
  return [] unless limit.positive?

  query = params[:q]
  query = nil if query == ''

  user = params[:user].nil? ? nil : database.connection[:users][{ name: params[:user] }]

  repositories = query ? catalog(user).grep(:name, "%#{query}%") : catalog(user)
  repositories.limit(limit).select_map(::Sequel[:repositories][:name])
end

#valid_token?(token) ⇒ Boolean



305
306
307
308
309
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 305

def valid_token?(token)
  !database.connection[:authentication_tokens].where(token_checksum: checksum(token)).where do
    expire_at > Sequel::CURRENT_TIMESTAMP
  end.empty?
end