engines/zypper_auth/lib/zypper_auth/engine.rb
module ZypperAuth
class << self
def auth_logger
Thread.current[:logger] ||= ::Logger.new('/var/lib/rmt/zypper_auth.log')
Thread.current[:logger].reopen
Thread.current[:logger]
end
def verify_instance(request, logger, system)
return false unless request.headers['X-Instance-Data']
instance_data = Base64.decode64(request.headers['X-Instance-Data'].to_s)
base_product = system.products.find_by(product_type: 'base')
return false unless base_product
# check the cache for the system (20 min)
cache_key = [request.remote_ip, system.login, base_product.id].join('-')
cache_path = File.join(Rails.application.config.repo_cache_dir, cache_key)
if File.exist?(cache_path)
# only update registry cache key
InstanceVerification.update_cache(request.remote_ip, system.login, nil, registry: true)
return true
end
verification_provider = InstanceVerification.provider.new(
logger,
request,
base_product.attributes.symbolize_keys.slice(:identifier, :version, :arch, :release_type),
instance_data
)
is_valid = verification_provider.instance_valid?
# update repository and registry cache
InstanceVerification.update_cache(request.remote_ip, system.login, base_product.id)
is_valid
rescue InstanceVerification::Exception => e
message = ''
if system.proxy_byos
result = SccProxy.scc_check_subscription_expiration(request.headers, system.login, system.system_token, logger)
if result[:is_active]
InstanceVerification.update_cache(request.remote_ip, system.login, base_product.id)
return true
end
message = result[:message]
else
message = e.message
end
details = [ "System login: #{system.login}", "IP: #{request.remote_ip}" ]
details << "Instance ID: #{verification_provider.instance_id}" if verification_provider.instance_id
details << "Billing info: #{verification_provider.instance_billing_info}" if verification_provider.instance_billing_info
ZypperAuth.auth_logger.info <<~LOGMSG
Access to the repos denied: #{message}
#{details.join(', ')}
LOGMSG
false
rescue StandardError => e
logger.error('Unexpected instance verification error has occurred:')
logger.error(e.message)
logger.error("System login: #{system.login}, IP: #{request.remote_ip}")
logger.error('Backtrace:')
logger.error(e.backtrace)
false
end
end
class Engine < ::Rails::Engine
isolate_namespace ZypperAuth
config.generators.api_only = true
config.after_initialize do
::V3::ServiceSerializer.class_eval do
alias_method :original_url, :url
def url
original_url = original_url()
url = URI(original_url)
"plugin:/susecloud?credentials=#{object.name}&path=" + url.path
end
end
# replaces URLs in API response JSON
Api::Connect::V3::Systems::ActivationsController.class_eval do
def index
respond_with(
@system.activations,
each_serializer: ::V3::ActivationSerializer,
base_url: request.base_url,
include: '*.*'
)
end
end
# replaces URLs in API response JSON
Api::Connect::V3::Systems::ProductsController.class_eval do
def render_service
status = ((request.put? || request.post?) ? 201 : 200)
# manually setting request method, so respond_with actually renders content also for PUT
request.instance_variable_set(:@request_method, 'GET')
respond_with(
@product.service,
serializer: ::V3::ServiceSerializer,
base_url: request.base_url,
obsoleted_service_name: @obsoleted_service_name,
status: status
)
end
end
RMT::Misc.class_eval do
class << self
alias_method :original_make_repo_url, :make_repo_url
def make_repo_url(base_url, repo_local_path, service_name = nil)
original_url = original_make_repo_url(base_url, repo_local_path, service_name)
url = URI(original_url)
"plugin:/susecloud?credentials=#{service_name}&path=" + url.path
end
end
end
ServicesController.class_eval do
# additional validation for zypper service XML controller
before_action :verify_instance
def verify_instance
unless ZypperAuth.verify_instance(request, logger, @system)
render(xml: { error: 'Instance verification failed' }, status: 403)
end
end
end
StrictAuthentication::AuthenticationController.class_eval do
alias_method :original_path_allowed?, :path_allowed?
# additional validation for strict_authentication auth subrequest
def path_allowed?(path)
return false unless original_path_allowed?(path)
ZypperAuth.verify_instance(request, logger, @system)
end
end
end
end
end