require 'json' module SoarAuthenticationToken class ConfigRotator attr_accessor :maximum_number_of_public_keys def initialize @maximum_number_of_public_keys = 3 end def rotate_json_config_files(generator_file_name:, validator_file_name:) generator_config = JSON.parse(File.read(generator_file_name)) validator_config = JSON.parse(File.read(validator_file_name)) generator_config, validator_config = rotate_configs(generator_config: generator_config, validator_config: validator_config) File.open(generator_file_name,"w") do |f| f.write(JSON.pretty_generate generator_config) f.close end File.open(validator_file_name,"w") do |f| f.write(JSON.pretty_generate validator_config) f.close end end def rotate_configs(generator_config:, validator_config:) private_key, public_key = KeypairGenerator.new.generate key_description = generate_keypair_description updated_generator_config = rotate_generator_config(config: generator_config,new_private_key: private_key,new_key_description: key_description) updated_validator_config = rotate_validator_config(config: validator_config,new_public_key: public_key, new_key_description: key_description) raise 'generated configuration does not match' unless configurations_match_and_valid?(generator_config: updated_generator_config,validator_config: updated_validator_config) [updated_generator_config, updated_validator_config] end def rotate_generator_config(config: ,new_private_key: ,new_key_description:) validate_generator_config(config) new_config = config.dup new_config['auth_token_generator']['private_key'] = new_private_key new_config['auth_token_generator']['key_description'] = new_key_description new_config end def rotate_validator_config(config: ,new_public_key: ,new_key_description:) validate_validator_config(config) new_config = config.dup trim_public_keys(new_config) new_config['auth_token_validator']['keys'][new_key_description] = { 'public_key' => new_public_key } new_config end def configurations_match_and_valid?(generator_config:,validator_config:) validate_generator_config(generator_config) validate_validator_config(validator_config) test_token = generate_test_token(generator_config) validate_test_token(validator_config,test_token) end private def trim_public_keys(config) traversed_keys = 0 config['auth_token_validator']['keys'].sort.reverse_each do |key_name, key_data| traversed_keys += 1 config['auth_token_validator']['keys'].delete(key_name) if traversed_keys >= @maximum_number_of_public_keys end end def generate_keypair_description "KEYPAIR_#{Time.now.strftime("%Y%m%dT%H%M%S")}" end def generate_test_token(generator_config) generator = SoarAuthenticationToken::TokenGenerator.new(generator_config) generator.inject_store_provider(DummyStorageClient.new) token, meta = generator.generate(authenticated_identifier: 'test') token end def validate_test_token(validator_config,test_token) validator = SoarAuthenticationToken::TokenValidator.new(validator_config) validator.inject_store_provider(DummyStorageClient.new) validity, meta, message = validator.validate(authentication_token: test_token) validity end def validate_generator_config(config) raise ArgumentError, 'private_key not in config' unless config['auth_token_generator']['private_key'] end def validate_validator_config(config) raise ArgumentError, 'keys not in config' unless config['auth_token_validator']['keys'] end end class DummyStorageClient def initialize(configuration = {}) end def store_failure(state = true) end def store_failure? false end def add(token_identifier:, authenticated_identifier:, token_issue_time:, token_expiry_time:, flow_identifier: nil) end def remove(token_identifier:, flow_identifier: nil) true end def token_exist?(token_identifier:, authenticated_identifier:, token_issue_time:, token_expiry_time:, flow_identifier: nil) true end def remove_tokens_for(authenticated_identifier:, flow_identifier: nil) true end def list_tokens_for(authenticated_identifier:, flow_identifier: nil) nil end end end