smart_proxy_container_gateway 1.0.0 → 1.0.5
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 +5 -5
- data/lib/smart_proxy_container_gateway/container_gateway.rb +15 -4
- data/lib/smart_proxy_container_gateway/container_gateway_api.rb +89 -32
- data/lib/smart_proxy_container_gateway/container_gateway_main.rb +102 -25
- data/lib/smart_proxy_container_gateway/foreman_api.rb +13 -5
- data/lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb +65 -0
- data/lib/smart_proxy_container_gateway/version.rb +1 -1
- data/settings.d/container_gateway.yml.example +2 -0
- metadata +17 -17
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: f7f64eda929055e1dbf8e33f6f25797f5f01eaec99fc44c68b48cdbb54e3ac24
         | 
| 4 | 
            +
              data.tar.gz: 3b82489df2523cc112474f8f88925959123db0ea34848462dcaa3a15aa2f01f0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 7275b37d4d4e2de7be47377f45790277df201752f429a3d1bea719a7f644b9a59e7b39a312f75d4b6c75d66c2ada8a21970cab2b6ee665ee11c45dfd739d2905
         | 
| 7 | 
            +
              data.tar.gz: 0f12062658a3840ddccb627e1e2ef2602e3e53b03b218684ff2f6a733504107a49329237d56f6a0b5ed6669509b422622459f84dc2c66484ddb99f5ec10bfa71
         | 
| @@ -9,10 +9,21 @@ module Proxy | |
| 9 9 | 
             
                                   :katello_registry_path => '/v2/',
         | 
| 10 10 | 
             
                                   :sqlite_db_path => '/var/lib/foreman-proxy/smart_proxy_container_gateway.db'
         | 
| 11 11 |  | 
| 12 | 
            -
                   | 
| 13 | 
            -
             | 
| 14 | 
            -
                   | 
| 15 | 
            -
             | 
| 12 | 
            +
                  # Load defaults that copy values from SETTINGS. This is done as
         | 
| 13 | 
            +
                  # programmable settings since SETTINGS isn't initialized during plugin
         | 
| 14 | 
            +
                  # loading.
         | 
| 15 | 
            +
                  load_programmable_settings do |settings|
         | 
| 16 | 
            +
                    settings[:pulp_client_ssl_ca] ||= SETTINGS.foreman_ssl_ca
         | 
| 17 | 
            +
                    settings[:pulp_client_ssl_cert] ||= SETTINGS.foreman_ssl_cert
         | 
| 18 | 
            +
                    settings[:pulp_client_ssl_key] ||= SETTINGS.foreman_ssl_key
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  # TODO: sqlite_db_path should able be readable or creatable. There's no
         | 
| 22 | 
            +
                  # test for creatable
         | 
| 23 | 
            +
                  validate_readable :pulp_client_ssl_ca, :pulp_client_ssl_cert, :pulp_client_ssl_key
         | 
| 24 | 
            +
                  validate :pulp_endpoint, url: true
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  rackup_path File.join(__dir__, 'container_gateway_http_config.ru')
         | 
| 16 27 | 
             
                end
         | 
| 17 28 | 
             
              end
         | 
| 18 29 | 
             
            end
         | 
| @@ -2,7 +2,6 @@ require 'sinatra' | |
| 2 2 | 
             
            require 'smart_proxy_container_gateway/container_gateway'
         | 
| 3 3 | 
             
            require 'smart_proxy_container_gateway/container_gateway_main'
         | 
| 4 4 | 
             
            require 'smart_proxy_container_gateway/foreman_api'
         | 
| 5 | 
            -
            require 'sequel'
         | 
| 6 5 | 
             
            require 'sqlite3'
         | 
| 7 6 |  | 
| 8 7 | 
             
            module Proxy
         | 
| @@ -10,7 +9,7 @@ module Proxy | |
| 10 9 | 
             
                class Api < ::Sinatra::Base
         | 
| 11 10 | 
             
                  include ::Proxy::Log
         | 
| 12 11 | 
             
                  helpers ::Proxy::Helpers
         | 
| 13 | 
            -
                   | 
| 12 | 
            +
                  helpers ::Sinatra::Authorization::Helpers
         | 
| 14 13 |  | 
| 15 14 | 
             
                  get '/v1/_ping/?' do
         | 
| 16 15 | 
             
                    Proxy::ContainerGateway.ping
         | 
| @@ -18,6 +17,7 @@ module Proxy | |
| 18 17 |  | 
| 19 18 | 
             
                  get '/v2/?' do
         | 
| 20 19 | 
             
                    if auth_header.present? && (auth_header.unauthorized_token? || auth_header.valid_user_token?)
         | 
| 20 | 
            +
                      response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
         | 
| 21 21 | 
             
                      Proxy::ContainerGateway.ping
         | 
| 22 22 | 
             
                    else
         | 
| 23 23 | 
             
                      redirect_authorization_headers
         | 
| @@ -26,31 +26,32 @@ module Proxy | |
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  get '/v2/:repository/manifests/:tag/?' do
         | 
| 29 | 
            -
                     | 
| 30 | 
            -
                      redirect_authorization_headers
         | 
| 31 | 
            -
                      halt 401, "unauthorized"
         | 
| 32 | 
            -
                    end
         | 
| 29 | 
            +
                    handle_repo_auth(params, auth_header, request)
         | 
| 33 30 | 
             
                    redirection_location = Proxy::ContainerGateway.manifests(params[:repository], params[:tag])
         | 
| 34 31 | 
             
                    redirect to(redirection_location)
         | 
| 35 32 | 
             
                  end
         | 
| 36 33 |  | 
| 37 34 | 
             
                  get '/v2/:repository/blobs/:digest/?' do
         | 
| 38 | 
            -
                     | 
| 39 | 
            -
                      redirect_authorization_headers
         | 
| 40 | 
            -
                      halt 401, "unauthorized"
         | 
| 41 | 
            -
                    end
         | 
| 35 | 
            +
                    handle_repo_auth(params, auth_header, request)
         | 
| 42 36 | 
             
                    redirection_location = Proxy::ContainerGateway.blobs(params[:repository], params[:digest])
         | 
| 43 37 | 
             
                    redirect to(redirection_location)
         | 
| 44 38 | 
             
                  end
         | 
| 45 39 |  | 
| 46 40 | 
             
                  get '/v1/search/?' do
         | 
| 47 41 | 
             
                    # Checks for podman client and issues a 404 in that case. Podman
         | 
| 48 | 
            -
                    # examines the response from a / | 
| 42 | 
            +
                    # examines the response from a /v1/search request. If the result
         | 
| 49 43 | 
             
                    # is a 4XX, it will then proceed with a request to /_catalog
         | 
| 50 44 | 
             
                    if !request.env['HTTP_USER_AGENT'].nil? && request.env['HTTP_USER_AGENT'].downcase.include?('libpod')
         | 
| 51 45 | 
             
                      halt 404, "not found"
         | 
| 52 46 | 
             
                    end
         | 
| 53 47 |  | 
| 48 | 
            +
                    if auth_header.present? && !auth_header.blank?
         | 
| 49 | 
            +
                      username = auth_header.v1_foreman_authorized_username
         | 
| 50 | 
            +
                      if username.nil?
         | 
| 51 | 
            +
                        halt 401, "unauthorized"
         | 
| 52 | 
            +
                      end
         | 
| 53 | 
            +
                      params[:user] = username
         | 
| 54 | 
            +
                    end
         | 
| 54 55 | 
             
                    repositories = Proxy::ContainerGateway.v1_search(params)
         | 
| 55 56 |  | 
| 56 57 | 
             
                    content_type :json
         | 
| @@ -58,13 +59,23 @@ module Proxy | |
| 58 59 | 
             
                  end
         | 
| 59 60 |  | 
| 60 61 | 
             
                  get '/v2/_catalog/?' do
         | 
| 61 | 
            -
                     | 
| 62 | 
            -
                     | 
| 63 | 
            -
             | 
| 62 | 
            +
                    catalog = []
         | 
| 63 | 
            +
                    if auth_header.present?
         | 
| 64 | 
            +
                      if auth_header.unauthorized_token?
         | 
| 65 | 
            +
                        catalog = Proxy::ContainerGateway.catalog
         | 
| 66 | 
            +
                      elsif auth_header.valid_user_token?
         | 
| 67 | 
            +
                        catalog = Proxy::ContainerGateway.catalog(auth_header.user)
         | 
| 68 | 
            +
                      else
         | 
| 69 | 
            +
                        redirect_authorization_headers
         | 
| 70 | 
            +
                        halt 401, "unauthorized"
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
                    else
         | 
| 73 | 
            +
                      redirect_authorization_headers
         | 
| 74 | 
            +
                      halt 401, "unauthorized"
         | 
| 75 | 
            +
                    end
         | 
| 64 76 |  | 
| 65 | 
            -
                  get '/v2/unauthenticated_repository_list/?' do
         | 
| 66 77 | 
             
                    content_type :json
         | 
| 67 | 
            -
                    { repositories:  | 
| 78 | 
            +
                    { repositories: catalog }.to_json
         | 
| 68 79 | 
             
                  end
         | 
| 69 80 |  | 
| 70 81 | 
             
                  get '/v2/token' do
         | 
| @@ -72,8 +83,8 @@ module Proxy | |
| 72 83 |  | 
| 73 84 | 
             
                    unless auth_header.present? && auth_header.basic_auth?
         | 
| 74 85 | 
             
                      one_year = (60 * 60 * 24 * 365)
         | 
| 75 | 
            -
                      return { token: AuthorizationHeader::UNAUTHORIZED_TOKEN, issued_at: Time.now,
         | 
| 76 | 
            -
            expires_at: Time.now + one_year }.to_json
         | 
| 86 | 
            +
                      return { token: AuthorizationHeader::UNAUTHORIZED_TOKEN, issued_at: Time.now.iso8601,
         | 
| 87 | 
            +
                               expires_at: (Time.now + one_year).iso8601 }.to_json
         | 
| 77 88 | 
             
                    end
         | 
| 78 89 |  | 
| 79 90 | 
             
                    token_response = ForemanApi.new.fetch_token(auth_header.raw_header, request.params)
         | 
| @@ -83,28 +94,57 @@ expires_at: Time.now + one_year }.to_json | |
| 83 94 | 
             
                      token_response_body = JSON.parse(token_response.body)
         | 
| 84 95 | 
             
                      ContainerGateway.insert_token(request.params['account'], token_response_body['token'],
         | 
| 85 96 | 
             
                                                    token_response_body['expires_at'])
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                      repo_response = ForemanApi.new.fetch_user_repositories(auth_header.raw_header, request.params)
         | 
| 99 | 
            +
                      if repo_response.code.to_i != 200
         | 
| 100 | 
            +
                        halt repo_response.code.to_i, repo_response.body
         | 
| 101 | 
            +
                      else
         | 
| 102 | 
            +
                        ContainerGateway.update_user_repositories(request.params['account'],
         | 
| 103 | 
            +
                                                                  JSON.parse(repo_response.body)['repositories'])
         | 
| 104 | 
            +
                      end
         | 
| 86 105 | 
             
                      return token_response_body.to_json
         | 
| 87 106 | 
             
                    end
         | 
| 88 107 | 
             
                  end
         | 
| 89 108 |  | 
| 90 | 
            -
                   | 
| 91 | 
            -
                     | 
| 92 | 
            -
             | 
| 93 | 
            -
                     | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
                     | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 109 | 
            +
                  get '/users/?' do
         | 
| 110 | 
            +
                    do_authorize_any
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                    content_type :json
         | 
| 113 | 
            +
                    { users: User.map(:name) }.to_json
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  put '/user_repository_mapping/?' do
         | 
| 117 | 
            +
                    do_authorize_any
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    ContainerGateway.update_user_repo_mapping(params)
         | 
| 120 | 
            +
                    {}
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  put '/repository_list/?' do
         | 
| 124 | 
            +
                    do_authorize_any
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                    repositories = params['repositories'].nil? ? [] : params['repositories']
         | 
| 127 | 
            +
                    ContainerGateway.update_repository_list(repositories)
         | 
| 128 | 
            +
                    {}
         | 
| 104 129 | 
             
                  end
         | 
| 105 130 |  | 
| 106 131 | 
             
                  private
         | 
| 107 132 |  | 
| 133 | 
            +
                  def handle_repo_auth(params, auth_header, request)
         | 
| 134 | 
            +
                    user_token_is_valid = false
         | 
| 135 | 
            +
                    # FIXME: Getting unauthenticated token here...
         | 
| 136 | 
            +
                    if auth_header.present? && auth_header.valid_user_token?
         | 
| 137 | 
            +
                      user_token_is_valid = true
         | 
| 138 | 
            +
                      username = auth_header.user.name
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                    username = request.params['account'] if username.nil?
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    return if Proxy::ContainerGateway.authorized_for_repo?(params[:repository], user_token_is_valid, username)
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    redirect_authorization_headers
         | 
| 145 | 
            +
                    halt 401, "unauthorized"
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
             | 
| 108 148 | 
             
                  def redirect_authorization_headers
         | 
| 109 149 | 
             
                    response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
         | 
| 110 150 | 
             
                    response.headers['Www-Authenticate'] = "Bearer realm=\"https://#{request.host}/v2/token\"," \
         | 
| @@ -123,6 +163,10 @@ expires_at: Time.now + one_year }.to_json | |
| 123 163 | 
             
                      @value = value || ''
         | 
| 124 164 | 
             
                    end
         | 
| 125 165 |  | 
| 166 | 
            +
                    def user
         | 
| 167 | 
            +
                      ContainerGateway.token_user(@value.split(' ')[1])
         | 
| 168 | 
            +
                    end
         | 
| 169 | 
            +
             | 
| 126 170 | 
             
                    def valid_user_token?
         | 
| 127 171 | 
             
                      token_auth? && ContainerGateway.valid_token?(@value.split(' ')[1])
         | 
| 128 172 | 
             
                    end
         | 
| @@ -146,6 +190,19 @@ expires_at: Time.now + one_year }.to_json | |
| 146 190 | 
             
                    def basic_auth?
         | 
| 147 191 | 
             
                      @value.split(' ')[0] == 'Basic'
         | 
| 148 192 | 
             
                    end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    def blank?
         | 
| 195 | 
            +
                      Base64.decode64(@value.split(' ')[1]) == ':'
         | 
| 196 | 
            +
                    end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                    # A special case for the V1 API.  Defer authentication to Foreman and return the username. `nil` if not authorized.
         | 
| 199 | 
            +
                    def v1_foreman_authorized_username
         | 
| 200 | 
            +
                      username = Base64.decode64(@value.split(' ')[1]).split(':')[0]
         | 
| 201 | 
            +
                      auth_response = ForemanApi.new.fetch_token(raw_header, { 'account' => username })
         | 
| 202 | 
            +
                      return username if auth_response.code.to_i == 200 && (JSON.parse(auth_response.body)['token'] != 'unauthenticated')
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                      nil
         | 
| 205 | 
            +
                    end
         | 
| 149 206 | 
             
                  end
         | 
| 150 207 | 
             
                end
         | 
| 151 208 | 
             
              end
         | 
| @@ -1,15 +1,17 @@ | |
| 1 1 | 
             
            require 'net/http'
         | 
| 2 2 | 
             
            require 'uri'
         | 
| 3 3 | 
             
            require 'digest'
         | 
| 4 | 
            -
             | 
| 4 | 
            +
            require 'sequel'
         | 
| 5 5 | 
             
            module Proxy
         | 
| 6 6 | 
             
              module ContainerGateway
         | 
| 7 7 | 
             
                extend ::Proxy::Util
         | 
| 8 8 | 
             
                extend ::Proxy::Log
         | 
| 9 9 |  | 
| 10 10 | 
             
                class << self
         | 
| 11 | 
            +
                  Sequel.extension :migration, :core_extensions
         | 
| 11 12 | 
             
                  def pulp_registry_request(uri)
         | 
| 12 13 | 
             
                    http_client = Net::HTTP.new(uri.host, uri.port)
         | 
| 14 | 
            +
                    http_client.ca_file = pulp_ca
         | 
| 13 15 | 
             
                    http_client.cert = pulp_cert
         | 
| 14 16 | 
             
                    http_client.key = pulp_key
         | 
| 15 17 | 
             
                    http_client.use_ssl = true
         | 
| @@ -48,7 +50,8 @@ module Proxy | |
| 48 50 |  | 
| 49 51 | 
             
                    repo_count = 0
         | 
| 50 52 | 
             
                    repositories = []
         | 
| 51 | 
            -
                     | 
| 53 | 
            +
                    user = params[:user].nil? ? nil : User.find(name: params[:user])
         | 
| 54 | 
            +
                    Proxy::ContainerGateway.catalog(user).each do |repo_name|
         | 
| 52 55 | 
             
                      break if repo_count >= params[:n]
         | 
| 53 56 |  | 
| 54 57 | 
             
                      if params[:q].nil? || params[:q] == "" || repo_name.include?(params[:q])
         | 
| @@ -59,48 +62,103 @@ module Proxy | |
| 59 62 | 
             
                    repositories
         | 
| 60 63 | 
             
                  end
         | 
| 61 64 |  | 
| 62 | 
            -
                  def catalog
         | 
| 63 | 
            -
                     | 
| 65 | 
            +
                  def catalog(user = nil)
         | 
| 66 | 
            +
                    if user.nil?
         | 
| 67 | 
            +
                      unauthenticated_repos
         | 
| 68 | 
            +
                    else
         | 
| 69 | 
            +
                      (unauthenticated_repos + user.repositories_dataset.map(:name)).sort
         | 
| 70 | 
            +
                    end
         | 
| 64 71 | 
             
                  end
         | 
| 65 72 |  | 
| 66 73 | 
             
                  def unauthenticated_repos
         | 
| 67 | 
            -
                     | 
| 68 | 
            -
                    conn[:unauthenticated_repositories].order(:name).map(:name)
         | 
| 74 | 
            +
                    Repository.where(auth_required: false).order(:name).map(:name)
         | 
| 69 75 | 
             
                  end
         | 
| 70 76 |  | 
| 71 | 
            -
                   | 
| 72 | 
            -
             | 
| 73 | 
            -
                     | 
| 74 | 
            -
                     | 
| 75 | 
            -
                     | 
| 76 | 
            -
                       | 
| 77 | 
            +
                  # Replaces the entire list of repositories
         | 
| 78 | 
            +
                  def update_repository_list(repo_list)
         | 
| 79 | 
            +
                    RepositoryUser.dataset.delete
         | 
| 80 | 
            +
                    Repository.dataset.delete
         | 
| 81 | 
            +
                    repo_list.each do |repo|
         | 
| 82 | 
            +
                      Repository.find_or_create(name: repo['repository'],
         | 
| 83 | 
            +
                                                auth_required: repo['auth_required'].to_s.downcase == "true")
         | 
| 84 | 
            +
                    end
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  # Replaces the entire user-repo mapping for all logged-in users
         | 
| 88 | 
            +
                  def update_user_repo_mapping(user_repo_maps)
         | 
| 89 | 
            +
                    # Get hash map of all users and their repositories
         | 
| 90 | 
            +
                    # Ex: {"users"=> [{"admin"=>[{"repository"=>"repo", "auth_required"=>"true"}]}]}
         | 
| 91 | 
            +
                    # Go through list of repositories and add them to the DB
         | 
| 92 | 
            +
                    RepositoryUser.dataset.delete
         | 
| 93 | 
            +
                    user_repo_maps['users'].each do |user_repo_map|
         | 
| 94 | 
            +
                      user_repo_map.each do |user, repos|
         | 
| 95 | 
            +
                        repos.each do |repo|
         | 
| 96 | 
            +
                          found_repo = Repository.find(name: repo['repository'],
         | 
| 97 | 
            +
                                                       auth_required: repo['auth_required'].to_s.downcase == "true")
         | 
| 98 | 
            +
                          if found_repo.nil?
         | 
| 99 | 
            +
                            logger.warn("#{repo['repository']} does not exist in this smart proxy's environments")
         | 
| 100 | 
            +
                          elsif found_repo.auth_required
         | 
| 101 | 
            +
                            found_repo.add_user(User.find(name: user))
         | 
| 102 | 
            +
                          end
         | 
| 103 | 
            +
                        end
         | 
| 104 | 
            +
                      end
         | 
| 77 105 | 
             
                    end
         | 
| 78 106 | 
             
                  end
         | 
| 79 107 |  | 
| 80 | 
            -
                   | 
| 81 | 
            -
             | 
| 82 | 
            -
                     | 
| 83 | 
            -
                     | 
| 108 | 
            +
                  # Replaces the user-repo mapping for a single user
         | 
| 109 | 
            +
                  def update_user_repositories(username, repositories)
         | 
| 110 | 
            +
                    user = User.where(name: username).first
         | 
| 111 | 
            +
                    user.remove_all_repositories
         | 
| 112 | 
            +
                    repositories.each do |repo_name|
         | 
| 113 | 
            +
                      found_repo = Repository.find(name: repo_name)
         | 
| 114 | 
            +
                      if found_repo.nil?
         | 
| 115 | 
            +
                        logger.warn("#{repo_name} does not exist in this smart proxy's environments")
         | 
| 116 | 
            +
                      elsif user.repositories_dataset.where(name: repo_name).first.nil? && found_repo.auth_required
         | 
| 117 | 
            +
                        user.add_repository(found_repo)
         | 
| 118 | 
            +
                      end
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  def authorized_for_repo?(repo_name, user_token_is_valid, username = nil)
         | 
| 123 | 
            +
                    repository = Repository.where(name: repo_name).first
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    # Repository doesn't exist
         | 
| 126 | 
            +
                    return false if repository.nil?
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    # Repository doesn't require auth
         | 
| 129 | 
            +
                    return true unless repository.auth_required
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    if username && user_token_is_valid && repository.auth_required
         | 
| 132 | 
            +
                      # User is logged in and has access to the repository
         | 
| 133 | 
            +
                      user = User.find(name: username)
         | 
| 134 | 
            +
                      return !user.repositories_dataset.where(name: repo_name).first.nil?
         | 
| 135 | 
            +
                    end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    false
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def token_user(token)
         | 
| 141 | 
            +
                    User[AuthenticationToken.find(token_checksum: Digest::SHA256.hexdigest(token)).user_id]
         | 
| 84 142 | 
             
                  end
         | 
| 85 143 |  | 
| 86 144 | 
             
                  def valid_token?(token)
         | 
| 87 | 
            -
                     | 
| 88 | 
            -
                    tokens.where(token_checksum: Digest::SHA256.hexdigest(token)).where do
         | 
| 145 | 
            +
                    AuthenticationToken.where(token_checksum: Digest::SHA256.hexdigest(token)).where do
         | 
| 89 146 | 
             
                      expire_at > Sequel::CURRENT_TIMESTAMP
         | 
| 90 147 | 
             
                    end.count.positive?
         | 
| 91 148 | 
             
                  end
         | 
| 92 149 |  | 
| 93 150 | 
             
                  def insert_token(username, token, expire_at_string, clear_expired_tokens: true)
         | 
| 94 | 
            -
                    tokens = initialize_db[:authentication_tokens]
         | 
| 95 151 | 
             
                    checksum = Digest::SHA256.hexdigest(token)
         | 
| 152 | 
            +
                    user = User.find_or_create(name: username)
         | 
| 96 153 |  | 
| 97 | 
            -
                     | 
| 98 | 
            -
                     | 
| 99 | 
            -
                     | 
| 154 | 
            +
                    AuthenticationToken.where(:token_checksum => checksum).delete
         | 
| 155 | 
            +
                    AuthenticationToken.create(token_checksum: checksum, expire_at: expire_at_string.to_s, user_id: user.id)
         | 
| 156 | 
            +
                    AuthenticationToken.where { expire_at < Sequel::CURRENT_TIMESTAMP }.delete if clear_expired_tokens
         | 
| 100 157 | 
             
                  end
         | 
| 101 158 |  | 
| 102 159 | 
             
                  def initialize_db
         | 
| 103 | 
            -
                     | 
| 160 | 
            +
                    file_path = Proxy::ContainerGateway::Plugin.settings.sqlite_db_path
         | 
| 161 | 
            +
                    conn = Sequel.connect("sqlite://#{file_path}")
         | 
| 104 162 | 
             
                    container_gateway_path = $LOAD_PATH.detect { |path| path.include? 'smart_proxy_container_gateway' }
         | 
| 105 163 | 
             
                    begin
         | 
| 106 164 | 
             
                      Sequel::Migrator.check_current(conn, "#{container_gateway_path}/smart_proxy_container_gateway/sequel_migrations")
         | 
| @@ -116,15 +174,34 @@ module Proxy | |
| 116 174 | 
             
                    Sequel::Migrator.run(db_connection, "#{container_gateway_path}/smart_proxy_container_gateway/sequel_migrations")
         | 
| 117 175 | 
             
                  end
         | 
| 118 176 |  | 
| 177 | 
            +
                  def pulp_ca
         | 
| 178 | 
            +
                    Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_ca
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
             | 
| 119 181 | 
             
                  def pulp_cert
         | 
| 120 | 
            -
                    OpenSSL::X509::Certificate.new(File. | 
| 182 | 
            +
                    OpenSSL::X509::Certificate.new(File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_cert))
         | 
| 121 183 | 
             
                  end
         | 
| 122 184 |  | 
| 123 185 | 
             
                  def pulp_key
         | 
| 124 186 | 
             
                    OpenSSL::PKey::RSA.new(
         | 
| 125 | 
            -
                      File. | 
| 187 | 
            +
                      File.read(Proxy::ContainerGateway::Plugin.settings.pulp_client_ssl_key)
         | 
| 126 188 | 
             
                    )
         | 
| 127 189 | 
             
                  end
         | 
| 128 190 | 
             
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                class Repository < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories])
         | 
| 193 | 
            +
                  many_to_many :users
         | 
| 194 | 
            +
                end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                class User < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:users])
         | 
| 197 | 
            +
                  many_to_many :repositories
         | 
| 198 | 
            +
                  one_to_many :authentication_tokens
         | 
| 199 | 
            +
                end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                class RepositoryUser < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:repositories_users]); end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                class AuthenticationToken < ::Sequel::Model(Proxy::ContainerGateway.initialize_db[:authentication_tokens])
         | 
| 204 | 
            +
                  many_to_one :users
         | 
| 205 | 
            +
                end
         | 
| 129 206 | 
             
              end
         | 
| 130 207 | 
             
            end
         | 
| @@ -3,15 +3,15 @@ require 'uri' | |
| 3 3 | 
             
            module Proxy
         | 
| 4 4 | 
             
              module ContainerGateway
         | 
| 5 5 | 
             
                class ForemanApi
         | 
| 6 | 
            -
                  def  | 
| 7 | 
            -
                     | 
| 8 | 
            -
                     | 
| 6 | 
            +
                  def registry_request(auth_header, params, suffix)
         | 
| 7 | 
            +
                    uri = URI.join(Proxy::SETTINGS.foreman_url, Proxy::ContainerGateway::Plugin.settings.katello_registry_path, suffix)
         | 
| 8 | 
            +
                    uri.query = process_params(params)
         | 
| 9 9 |  | 
| 10 | 
            -
                    req = Net::HTTP::Get.new( | 
| 10 | 
            +
                    req = Net::HTTP::Get.new(uri)
         | 
| 11 11 | 
             
                    req.add_field('Authorization', auth_header)
         | 
| 12 12 | 
             
                    req.add_field('Accept', 'application/json')
         | 
| 13 13 | 
             
                    req.content_type = 'application/json'
         | 
| 14 | 
            -
                    http = Net::HTTP.new( | 
| 14 | 
            +
                    http = Net::HTTP.new(uri.hostname, uri.port)
         | 
| 15 15 | 
             
                    http.use_ssl = true
         | 
| 16 16 |  | 
| 17 17 | 
             
                    http.request(req)
         | 
| @@ -21,6 +21,14 @@ module Proxy | |
| 21 21 | 
             
                    params = params_in.slice('scope', 'account').compact
         | 
| 22 22 | 
             
                    URI.encode_www_form(params)
         | 
| 23 23 | 
             
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def fetch_token(auth_header, params)
         | 
| 26 | 
            +
                    registry_request(auth_header, params, 'token')
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def fetch_user_repositories(auth_header, params)
         | 
| 30 | 
            +
                    registry_request(auth_header, params, '_catalog')
         | 
| 31 | 
            +
                  end
         | 
| 24 32 | 
             
                end
         | 
| 25 33 | 
             
              end
         | 
| 26 34 | 
             
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            Sequel.migration do
         | 
| 2 | 
            +
              up do
         | 
| 3 | 
            +
                # TODO: Should I be migrating the existing data?
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                create_table(:repositories) do
         | 
| 6 | 
            +
                  primary_key :id
         | 
| 7 | 
            +
                  String :name, null: false
         | 
| 8 | 
            +
                  Boolean :auth_required, null: false
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                create_table(:users) do
         | 
| 12 | 
            +
                  primary_key :id
         | 
| 13 | 
            +
                  String :name, null: false
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Migrate unauthenticated_repositories to the new repositories table (TODO: can I select `false` like that?)
         | 
| 17 | 
            +
                from(:repositories).insert(%i[name auth_required],
         | 
| 18 | 
            +
                                           from(:unauthenticated_repositories).select(:name, false))
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # Migrate names from authentication_tokens to the new users table
         | 
| 21 | 
            +
                from(:users).insert([:name], from(:authentication_tokens).select(:username))
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                alter_table(:authentication_tokens) do
         | 
| 24 | 
            +
                  add_foreign_key :user_id, :users
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # Populate the new user_id foreign key for all authentication_tokens
         | 
| 28 | 
            +
                from(:authentication_tokens).insert([:user_id],
         | 
| 29 | 
            +
                                                    from(:users).select(:id).where(name: self[:authentication_tokens][:username]))
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                alter_table(:authentication_tokens) do
         | 
| 32 | 
            +
                  drop_column :username
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                create_join_table(repository_id: :repositories, user_id: :users)
         | 
| 36 | 
            +
                drop_table :unauthenticated_repositories
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              down do
         | 
| 40 | 
            +
                alter_table(:authentication_tokens) do
         | 
| 41 | 
            +
                  add_column :username, String
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                # Repopulate the name column with usernames
         | 
| 45 | 
            +
                from(:authentication_tokens).update(username:
         | 
| 46 | 
            +
                                                    from(:users).select(:name).where(id: self[:authentication_tokens][:user_id]))
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                alter_table(:authentication_tokens) do
         | 
| 49 | 
            +
                  drop_foreign_key :user_id
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                create_table(:unauthenticated_repositories) do
         | 
| 53 | 
            +
                  primary_key :id
         | 
| 54 | 
            +
                  String :name, null: false
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                # Repopulate the unauthenticated_repositories table
         | 
| 58 | 
            +
                from(:unauthenticated_repositories).insert([:username],
         | 
| 59 | 
            +
                                                           from(:repositories).select(:name).where(auth_required: true))
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                drop_table :users
         | 
| 62 | 
            +
                drop_table :repositories
         | 
| 63 | 
            +
                drop_join_table(repository_id: :repositories, user_id: :users)
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -1,6 +1,8 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            :enabled: true
         | 
| 3 3 | 
             
            :pulp_endpoint: 'https://your_pulp_3_server_here.com'
         | 
| 4 | 
            +
            :pulp_client_ssl_ca: 'CA Cert for authenticating with Pulp'
         | 
| 4 5 | 
             
            :pulp_client_ssl_cert: 'X509 certificate for authenticating with Pulp'
         | 
| 5 6 | 
             
            :pulp_client_ssl_key: 'RSA private key for the Pulp certificate'
         | 
| 7 | 
            +
            :katello_registry_path: 'Katello container registry suffix, e.g., /v2/'
         | 
| 6 8 | 
             
            :sqlite_db_path: '/var/lib/foreman-proxy/smart_proxy_container_gateway.db'
         | 
    
        metadata
    CHANGED
    
    | @@ -1,41 +1,41 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: smart_proxy_container_gateway
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0. | 
| 4 | 
            +
              version: 1.0.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ian Ballou
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2021- | 
| 11 | 
            +
            date: 2021-06-14 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: sequel
         | 
| 15 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 16 | 
             
                requirements:
         | 
| 17 | 
            -
                - -  | 
| 17 | 
            +
                - - ">="
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 19 | 
             
                    version: '0'
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 | 
            -
                - -  | 
| 24 | 
            +
                - - ">="
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 26 | 
             
                    version: '0'
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 28 | 
             
              name: sqlite3
         | 
| 29 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 30 | 
             
                requirements:
         | 
| 31 | 
            -
                - -  | 
| 31 | 
            +
                - - ">="
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 33 | 
             
                    version: '0'
         | 
| 34 34 | 
             
              type: :runtime
         | 
| 35 35 | 
             
              prerelease: false
         | 
| 36 36 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 37 | 
             
                requirements:
         | 
| 38 | 
            -
                - -  | 
| 38 | 
            +
                - - ">="
         | 
| 39 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 40 | 
             
                    version: '0'
         | 
| 41 41 | 
             
            description: Pulp 3 container registry support for Foreman/Katello Smart-Proxy
         | 
| @@ -46,19 +46,20 @@ extra_rdoc_files: | |
| 46 46 | 
             
            - README.md
         | 
| 47 47 | 
             
            - LICENSE
         | 
| 48 48 | 
             
            files:
         | 
| 49 | 
            +
            - LICENSE
         | 
| 50 | 
            +
            - README.md
         | 
| 51 | 
            +
            - bundler.d/container_gateway.rb
         | 
| 49 52 | 
             
            - lib/smart_proxy_container_gateway.rb
         | 
| 53 | 
            +
            - lib/smart_proxy_container_gateway/container_gateway.rb
         | 
| 54 | 
            +
            - lib/smart_proxy_container_gateway/container_gateway_api.rb
         | 
| 55 | 
            +
            - lib/smart_proxy_container_gateway/container_gateway_http_config.ru
         | 
| 56 | 
            +
            - lib/smart_proxy_container_gateway/container_gateway_main.rb
         | 
| 50 57 | 
             
            - lib/smart_proxy_container_gateway/foreman_api.rb
         | 
| 51 58 | 
             
            - lib/smart_proxy_container_gateway/sequel_migrations/001_initial.rb
         | 
| 52 59 | 
             
            - lib/smart_proxy_container_gateway/sequel_migrations/002_auth_tokens.rb
         | 
| 53 | 
            -
            - lib/smart_proxy_container_gateway/ | 
| 60 | 
            +
            - lib/smart_proxy_container_gateway/sequel_migrations/003_authorization_reorg.rb
         | 
| 54 61 | 
             
            - lib/smart_proxy_container_gateway/version.rb
         | 
| 55 | 
            -
            - lib/smart_proxy_container_gateway/container_gateway.rb
         | 
| 56 | 
            -
            - lib/smart_proxy_container_gateway/container_gateway_api.rb
         | 
| 57 | 
            -
            - lib/smart_proxy_container_gateway/container_gateway_main.rb
         | 
| 58 62 | 
             
            - settings.d/container_gateway.yml.example
         | 
| 59 | 
            -
            - bundler.d/container_gateway.rb
         | 
| 60 | 
            -
            - README.md
         | 
| 61 | 
            -
            - LICENSE
         | 
| 62 63 | 
             
            homepage: http://github.com/ianballou/smart_proxy_container_gateway
         | 
| 63 64 | 
             
            licenses:
         | 
| 64 65 | 
             
            - GPLv3
         | 
| @@ -69,17 +70,16 @@ require_paths: | |
| 69 70 | 
             
            - lib
         | 
| 70 71 | 
             
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 71 72 | 
             
              requirements:
         | 
| 72 | 
            -
              - - ~>
         | 
| 73 | 
            +
              - - "~>"
         | 
| 73 74 | 
             
                - !ruby/object:Gem::Version
         | 
| 74 75 | 
             
                  version: '2.5'
         | 
| 75 76 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 76 77 | 
             
              requirements:
         | 
| 77 | 
            -
              - -  | 
| 78 | 
            +
              - - ">="
         | 
| 78 79 | 
             
                - !ruby/object:Gem::Version
         | 
| 79 80 | 
             
                  version: '0'
         | 
| 80 81 | 
             
            requirements: []
         | 
| 81 | 
            -
             | 
| 82 | 
            -
            rubygems_version: 2.0.14.1
         | 
| 82 | 
            +
            rubygems_version: 3.0.3
         | 
| 83 83 | 
             
            signing_key: 
         | 
| 84 84 | 
             
            specification_version: 4
         | 
| 85 85 | 
             
            summary: Pulp 3 container registry support for Foreman/Katello Smart-Proxy
         |