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

Returns:

  • (Boolean)


249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 249

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_repository_mapping(host_repo_maps) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 202

def build_host_repository_mapping(host_repo_maps)
  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|
      host = hosts[{ uuid: host_uuid }]
      next unless host

      repo_names = repos
                   .select { |repo| repo['auth_required'].to_s.downcase == "true" }
                   .map { |repo| repo['repository'] }

      repositories
        .where(name: repo_names, auth_required: true)
        .select(:id)
        .map { |repo| [repo[:id], host[:id]] }
    end
  end
  entries.flatten!(1)
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

Returns:

  • (Boolean)


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

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

#find_or_create_host(uuid) ⇒ Object



241
242
243
244
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 241

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



294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 294

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



282
283
284
285
286
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 282

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)
  end
end

#update_host_repositories(uuid, repositories) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 223

def update_host_repositories(uuid, repositories)
  host = find_or_create_host(uuid)
  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

Returns:

  • (Boolean)


288
289
290
291
292
# File 'lib/smart_proxy_container_gateway/container_gateway_main.rb', line 288

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