Skip to content

Commit

Permalink
Merge branch 'master' into fix_subs_after_migration
Browse files Browse the repository at this point in the history
  • Loading branch information
ngetahun authored May 16, 2024
2 parents b12b83e + 7982654 commit 82888f4
Show file tree
Hide file tree
Showing 41 changed files with 488 additions and 146 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ group :test do
gem 'ffaker', '<= 2.21.0' # Locked because of Ruby >= 3.0 dependency
gem 'rspec-its'
gem 'fakefs', '~> 1.4', require: 'fakefs/safe'
gem 'shoulda-matchers'
gem 'shoulda-matchers', '~> 4.5.1' # Locked because of Ruby >= 3.0 dependency
gem 'webmock'
gem 'fuubar'
gem 'timecop'
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ GEM
rubocop-thread_safety (~> 0.4)
sexp_processor (4.16.1)
shellany (0.0.1)
shoulda-matchers (4.4.1)
shoulda-matchers (4.5.1)
activesupport (>= 4.2.0)
simplecov (0.16.1)
docile (~> 1.1)
Expand Down Expand Up @@ -365,7 +365,7 @@ DEPENDENCIES
rubocop-ast (<= 1.17.0)
ruby_parser (< 3.20)
scc-codestyle (<= 0.5.0)
shoulda-matchers
shoulda-matchers (~> 4.5.1)
simplecov
spring
spring-commands-rspec
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
This tool allows you to mirror RPM repositories in your own private network.
Organization (mirroring) credentials are required to mirror SUSE repositories.

The [SLE RMT Book](https://documentation.suse.com/sles/15-SP5/html/SLES-all/book-rmt.html) contains
The [SUSE Linux Enterprise RMT Guide](https://documentation.suse.com/sles/html/SLES-all/book-rmt.html) contains
the end-user documentation for RMT. `man` pages for `rmt-cli` are located in the file [MANUAL.md](MANUAL.md).

If you would like to contribute to RMT, please see our [contribution guide](docs/CONTRIBUTING.md).
Expand Down
10 changes: 4 additions & 6 deletions app/controllers/api/connect/v3/systems/systems_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ def update
params[:online_at].each do |online_at|
dthours = online_at.split(':')
if dthours.count == 2
begin
@system_uptime = SystemUptime.create!(system_id: @system.id, online_at_day: dthours[0], online_at_hours: dthours[1])
logger.debug(N_("Added uptime information for system '%s'") % @system.id)
rescue ActiveRecord::RecordNotUnique
logger.debug(N_("Uptime information existing for system '%s'") % @system.id)
end
online_day = online_at.split(':')[0]
online_hours = online_at.split(':')[1]
@system.update_system_uptime(day: online_day,
hours: online_hours)
else
logger.error(N_("Uptime data is malformed '%s'") % online_at)
end
Expand Down
9 changes: 9 additions & 0 deletions app/models/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ def self.get_by_credentials(login, password)
where(login: login, password: password).order(:id)
end

def update_system_uptime(day: nil, hours: nil)
system_uptime = system_uptimes.find_by(online_at_day: day)
if system_uptime
system_uptime.update!(online_at_hours: hours)
else
system_uptimes.create!(online_at_day: day, online_at_hours: hours)
end
end

before_update do |system|
# reset SCC sync timestamp so that the system can be re-synced on change
system.scc_synced_at = nil
Expand Down
13 changes: 0 additions & 13 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,6 @@ class Application < Rails::Application
config.eager_load_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('app', 'validators')

# :nocov:
if defined?(Registry::Engine) && Rails.env.production?
# registry config needed
config.autoloader = :classic
config.registry_private_key = OpenSSL::PKey::RSA.new(
File.read('/etc/rmt/ssl/rmt-server.key')
)
config.registry_public_key = config.registry_private_key.public_key
config.access_policies = '/etc/rmt/access_policies.yml'
# registry config needed end
end
# :nocov:

# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
Expand Down
11 changes: 11 additions & 0 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

config.cache_config_file = '/var/lib/rmt/rmt-cache-trim.sh'
config.repo_cache_dir = '/run/rmt/cache/repository'
config.registry_cache_dir = '/run/rmt/cache/registry'
cache_config_content = [
"REPOSITORY_CLIENT_CACHE_DIRECTORY=#{config.repo_cache_dir}",
'REPOSITORY_CACHE_EXPIRY_MINUTES=20',
"REGISTRY_CLIENT_CACHE_DIRECTORY=#{config.registry_cache_dir}",
"REGISTRY_CACHE_EXPIRY_MINUTES=#{Settings[:registry].try(:token_expiration) || 480}" # 480: 8 hours in minutes
].join("\n")
File.write(config.cache_config_file, cache_config_content)

# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'

Expand Down
5 changes: 0 additions & 5 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@
config.action_controller.perform_caching = false
config.cache_store = :null_store

# for registry
config.access_policies = 'engines/registry/spec/data/access_policies.yml'
config.registry_private_key = OpenSSL::PKey::RSA.new(2048)
config.registry_public_key = config.registry_private_key.public_key
config.autoload_paths << Rails.root.join('engines/registry/spec/support/')
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false

Expand Down
13 changes: 13 additions & 0 deletions engines/instance_verification/config/environments/production.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# :nocov:
module InstanceVerification
class Application < Rails::Application
config.cache_config_file = '/var/lib/rmt/rmt-cache-trim.sh'
config.repo_cache_dir = '/run/rmt/cache/repository'
cache_config_content = [
"REPOSITORY_CLIENT_CACHE_DIRECTORY=#{config.repo_cache_dir}",
'REPOSITORY_CACHE_EXPIRY_MINUTES=20'
].join("\n")
File.write(config.cache_config_file, cache_config_content)
end
end
# :nocov:
10 changes: 10 additions & 0 deletions engines/instance_verification/config/environments/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
config.cache_config_file = Rails.root.join('engines/registry/spec/data/rmt-cache-trim.sh')
config.repo_cache_dir = 'repo/cache'
config.registry_cache_dir = 'registry/cache'
end
28 changes: 22 additions & 6 deletions engines/instance_verification/lib/instance_verification/engine.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
require 'fileutils'

module InstanceVerification
def self.update_cache(remote_ip, system_login, product_id, is_byos)
cache_key = [remote_ip, system_login, product_id].join('-')
# caches verification result to be used by zypper auth plugin
expire_cache_time = is_byos ? 24.hours : 20.minutes
Rails.cache.write(cache_key, true, expires_in: expire_cache_time)
def self.update_cache(remote_ip, system_login, product_id, registry: false)
# TODO: BYOS scenario
# to be addressed on a different PR
unless registry
InstanceVerification.write_cache_file(
Rails.application.config.repo_cache_dir,
[remote_ip, system_login, product_id].join('-')
)
end

InstanceVerification.write_cache_file(
Rails.application.config.registry_cache_dir,
[remote_ip, system_login].join('-')
)
end

def self.write_cache_file(cache_dir, cache_key)
FileUtils.mkdir_p(cache_dir)
FileUtils.touch(File.join(cache_dir, cache_key))
end

class Engine < ::Rails::Engine
Expand Down Expand Up @@ -95,7 +111,7 @@ def verify_base_product_activation(product)
)

raise 'Unspecified error' unless verification_provider.instance_valid?
InstanceVerification.update_cache(request.remote_ip, @system.login, product.id, @system.proxy_byos)
InstanceVerification.update_cache(request.remote_ip, @system.login, product.id)
end

# Verify that the base product doesn't change in the offline migration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
it 'class instance verification provider' do
expect(InstanceVerification::Providers::Example).to receive(:new)
.with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original
allow(File).to receive(:directory?)
allow(Dir).to receive(:mkdir)
allow(FileUtils).to receive(:touch)
post url, params: payload, headers: headers
end
end
Expand Down Expand Up @@ -110,10 +113,11 @@
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
.with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload_sap, instance_data).and_call_original

expect(Rails.cache).to receive(:write).with(
['127.0.0.1', system.login, product_sap.id].join('-'), true, expires_in: 20.minutes
)
allow(File).to receive(:directory?)
allow(Dir).to receive(:mkdir)
allow(FileUtils).to receive(:touch)
expect(InstanceVerification).to receive(:write_cache_file).once.with('repo/cache', "127.0.0.1-#{system.login}-#{product_sap.id}")
expect(InstanceVerification).to receive(:write_cache_file).once.with('registry/cache', "127.0.0.1-#{system.login}")
post url, params: payload_sap, headers: headers
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ After=rmt-server.service
Wants=rmt-server.service

[Service]
Type=simple
Type=oneshot
Restart=no
Environment="LOG_TO_JOURNALD=1" "LANG=en"
ExecStart=find /var/lib/rmt/tmp/cache -mmin +20 -type f -delete
EnvironmentFile=/var/lib/rmt/rmt-cache-trim.sh
ExecStart=find ${REPOSITORY_CLIENT_CACHE_DIRECTORY} -mmin +${REPOSITORY_CACHE_EXPIRY_MINUTES} -type f -delete
ExecStart=find ${REGISTRY_CLIENT_CACHE_DIRECTORY} -mmin +${REGISTRY_CACHE_EXPIRY_MINUTES} -type f -delete

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def basic_auth

authenticate_or_request_with_http_basic('SUSE Registry Authentication') do |login, password|
begin
@client = Registry::AuthenticatedClient.new(login, password)
@client = Registry::AuthenticatedClient.new(login, password, request.remote_ip)
rescue StandardError
logger.info _('Could not find system with login \"%{login}\" and password \"%{password}\"') %
{ login: login, password: password }
Expand Down
21 changes: 20 additions & 1 deletion engines/registry/app/models/registry/authenticated_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ class Registry::AuthenticatedClient

attr_reader :auth_strategy

def initialize(login, password)
def initialize(login, password, remote_ip)
raise Registry::Exceptions::InvalidCredentials.new(message: 'expired credentials', login: login) unless cache_file_exist?(remote_ip, login)

authenticate_by_system_credentials(login, password)
if @auth_strategy
Rails.logger.info("Authenticated '#{self}'")
Expand All @@ -22,4 +24,21 @@ def authenticate_by_system_credentials(login, password)
end
@auth_strategy
end

def cache_file_exist?(remote_ip, login)
registry_cache_key = [remote_ip, login].join('-')
registry_cache_path = File.join(cache_config['REGISTRY_CLIENT_CACHE_DIRECTORY'], registry_cache_key)
File.exist?(registry_cache_path)
end

def cache_config
cache_config_data = {}
File.open(Rails.application.config.cache_config_file, 'r') do |cache_config_file|
cache_config_file.each_line do |line|
line_data = line.split(/=|\n/)
cache_config_data[line_data[0]] = line_data[1]
end
end
cache_config_data
end
end
13 changes: 13 additions & 0 deletions engines/registry/config/environments/production.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# :nocov:
module Registry
class Application < Rails::Application
# registry config needed
config.autoloader = :classic
config.registry_private_key = OpenSSL::PKey::RSA.new(
File.read('/etc/rmt/ssl/internal-registry.key')
)
config.registry_public_key = config.registry_private_key.public_key
config.access_policies = '/etc/rmt/access_policies.yml'
end
end
# :nocov:
11 changes: 11 additions & 0 deletions engines/registry/config/environments/test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!

Rails.application.configure do
config.access_policies = 'engines/registry/spec/data/access_policies.yml'
config.registry_private_key = OpenSSL::PKey::RSA.new(2048)
config.registry_public_key = config.registry_private_key.public_key
config.autoload_paths << Rails.root.join('engines/registry/spec/support/')
end
28 changes: 28 additions & 0 deletions engines/registry/lib/registry/engine.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
module Registry
class << self
def remove_auth_cache(registry_cache_key)
cache_path = File.join(Rails.application.config.registry_cache_dir, registry_cache_key)
File.unlink(registry_cache_path) if File.exist?(cache_path)
end
end

class Engine < ::Rails::Engine
isolate_namespace Registry
config.generators.api_only = true

config.after_initialize do
Api::Connect::V3::Systems::ActivationsController.class_eval do
before_action :handle_auth_cache, only: %w[index]

def handle_auth_cache
unless ZypperAuth.verify_instance(request, logger, @system)
render(xml: { error: 'Instance verification failed' }, status: :forbidden)
end
end
end

Api::Connect::V3::Systems::SystemsController.class_eval do
before_action :remove_auth_cache, only: %w[deregister]

def remove_auth_cache
registry_cache_key = [request.remote_ip, @system.login].join('-')
Registry.remove_auth_cache(registry_cache_key)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module Registry
let(:auth_headers) { { 'Authorization' => ActionController::HttpAuthentication::Basic.encode_credentials(system.login, system.password) } }

it 'succeeds with login + password from secrets' do
allow_any_instance_of(AuthenticatedClient).to receive(:cache_file_exist?).and_return(true)
get('/api/registry/authorize', headers: auth_headers)

expect(response).to have_http_status(:ok)
Expand Down Expand Up @@ -76,6 +77,7 @@ module Registry
context 'with a valid token' do
it 'has catalog access' do
allow(File).to receive(:read).and_return(access_policy_content)
allow_any_instance_of(AuthenticatedClient).to receive(:cache_file_exist?).and_return(true)
get(
'/api/registry/authorize',
params: { service: 'SUSE Linux OCI Registry', scope: 'registry:catalog:*' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,40 @@

describe Registry::AuthenticatedClient do
describe '.new' do
context 'with system credentials' do
let(:system) { create(:system) }
let(:system) { create(:system) }

context 'with valid credentials' do
subject(:client) { described_class.new(system.login, system.password) }
context 'with invalid cache' do
before { allow_any_instance_of(described_class).to receive(:cache_file_exist?).and_return(false) }

it 'returns the auth strategy' do
expect(client.systems).to eq([system])
expect(client.auth_strategy).to eq(:system_credentials)
end
it 'raises an exception' do
expect { described_class.new(system.login, system.password, '127.0.0.1') }.to raise_error(
Registry::Exceptions::InvalidCredentials, /expired credentials/
)
end
end

context 'with valid cache' do
context 'with system credentials' do
before { allow_any_instance_of(described_class).to receive(:cache_file_exist?).and_return(true) }

# rubocop:disable RSpec/NestedGroups
context 'with valid credentials' do
subject(:client) { described_class.new(system.login, system.password, '127.0.0.1') }

it 'returns the auth strategy' do
expect(client.systems).to eq([system])
expect(client.auth_strategy).to eq(:system_credentials)
end
end

context 'with invalid password' do
subject(:client) { described_class.new(system.login, 'wrong') }
context 'with invalid password' do
subject(:client) { described_class.new(system.login, 'wrong', '127.0.0.1') }

it 'raises' do
expect { client }.to raise_error(Registry::Exceptions::InvalidCredentials)
it 'raises' do
expect { client }.to raise_error(Registry::Exceptions::InvalidCredentials)
end
end
# rubocop:enable RSpec/NestedGroups
end
end
end
Expand Down
Loading

0 comments on commit 82888f4

Please sign in to comment.