def add_authorized_key(ssh_keys): remote_public_key = ssh_keys.get_remote('public-ssh-key') hookenv.log("Adding key: " + remote_public_key) ssh.add_authorized_key(remote_public_key, utils.get_paths()['authorized-keys']) ssh_peer = {ssh_keys.get_remote('private-address'): remote_public_key} utils.update_config(config_path=utils.get_paths()['config'], new_ssh_peers=ssh_peer) ssh_keys.remove_state(ssh_keys.states.new_remote_public_key)
def configure_reprepro(mirrors, sign_key_fingerprint, sign_key_passphrase): """Create reprepro configuration files. The provided mirrors is a sequence of mirror.Mirror named tuples. """ paths = get_paths() # Explicitly pass owner and group for tests, otherwise root would be used. owner = group = getpass.getuser() # Render distributions file. target = str(paths['reprepro-conf'] / 'distributions') context = { 'mirrors': mirrors, 'sign_script': paths['bin'] / 'reprepro-sign-helper', } render(_DISTRIBUTIONS, target, context, owner=owner, group=group) # Render updates file. target = str(paths['reprepro-conf'] / 'updates') context = {'mirrors': mirrors} render(_UPDATES, target, context, owner=owner, group=group) # Update configuration. update_config(config_path=paths['config'], suites=[mirror.local_suite for mirror in mirrors], sign_key_id=sign_key_fingerprint) # Save the sign passphrase for the signing helper script. with paths['sign-passphrase'].open('w') as fh: fh.write(sign_key_passphrase)
def test_disable_not_enabled(self): """Disabling mirror when not configured is a no-op.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) config = paths['config'] disable_mirroring(get_paths=lambda: paths) self.assertFalse(config.exists()) self.assertFalse(config.with_suffix('.disabled').exists())
def test_configuration_files(self): """configure_reprepro writes rerepro config files.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) uri = 'https://*****:*****@example.com/ubuntu xenial main universe' configure_reprepro(uri, 'i386 amd64', 'ABABABAB', 'CDCDCDCD', 'very secret', get_paths=lambda: paths) self.assertEqual( textwrap.dedent('''\ Codename: xenial Label: xenial archive Components: main universe Architectures: i386 amd64 SignWith: ! {}/reprepro-sign-helper Update: update-repo '''.format(paths['bin'])), (paths['reprepro-conf'] / 'distributions').read_text()) self.assertEqual( textwrap.dedent('''\ Name: update-repo Method: https://user:[email protected]/ubuntu Suite: xenial Components: main universe Architectures: i386 amd64 VerifyRelease: ABABABAB '''), (paths['reprepro-conf'] / 'updates').read_text()) self.assertEqual({ 'suite': 'xenial', 'sign-key-id': 'CDCDCDCD' }, yaml.load(paths['config'].read_text()))
def make_reprepro_files(root_dir, mirrors): """A tiny wrapper around configure_reprepro with testing paths.""" paths = get_paths(root_dir=Path(root_dir)) with mock.patch('charms.archive_auth_mirror.repository.get_paths', return_value=paths): configure_reprepro(mirrors, sign_key_fingerprint, sign_key_passphrase) return paths
def configure_reprepro(mirror_uri, mirror_archs, mirror_key_fingerprint, sign_key_fingerprint, sign_key_passphrase, get_paths=get_paths): """Create reprepro configuration files.""" paths = get_paths() context = split_repository_uri(mirror_uri) context.update({ 'archs': mirror_archs, 'mirror_key': mirror_key_fingerprint, 'sign_key': sign_key_fingerprint, 'sign_script': paths['bin'] / 'reprepro-sign-helper' }) # explicitly pass owner and group for tests, otherwise root would be used owner = group = getpass.getuser() render('reprepro-distributions.j2', str(paths['reprepro-conf'] / 'distributions'), context, owner=owner, group=group) render('reprepro-updates.j2', str(paths['reprepro-conf'] / 'updates'), context, owner=owner, group=group) update_config(config_path=paths['config'], suite=context['suite'], sign_key_id=context['sign_key']) # save the sign passphrase for the signing helper script with paths['sign-passphrase'].open('w') as fh: fh.write(sign_key_passphrase)
def test_disable_twice(self): """disable_mirroring can be called multiple times.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) config = paths['config'] disable_mirroring(get_paths=lambda: paths) disable_mirroring(get_paths=lambda: paths) self.assertFalse(config.exists()) self.assertFalse(config.with_suffix('.disabled').exists())
def get_virtualhost_config(hookenv=hookenv): """Return the configuration for the static virtuahost.""" paths = get_paths() domain = get_virtualhost_name(hookenv=hookenv) return { 'domain': domain, 'document_root': str(paths['static']), 'basic_auth_file': str(paths['basic-auth'])}
def get_virtualhost_config(auth_backends=None, auth_cache_time=None, hookenv=hookenv): """Return the configuration for the static virtuahost.""" paths = get_paths() domain = get_virtualhost_name(hookenv=hookenv) return { 'domain': domain, 'document_root': str(paths['static']), 'auth_backends': auth_backends or [], 'auth_cache_time': auth_cache_time, 'basic_auth_file': str(paths['basic-auth'])}
def test_install_crontab(self): """install_crontab creates a crontab file""" root_dir = Path(self.fakes.fs.root.path) (root_dir / 'etc/cron.d').mkdir(parents=True) paths = get_paths(root_dir=root_dir) install_crontab(paths=paths) with paths['cron'].open() as fh: content = fh.read() script = paths['bin'] / 'mirror-archive' self.assertIn(str(script), content)
def test_detach_sign(self): """detach_sign creates a detached signature for a file.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) paths['gnupghome'].mkdir(parents=True) paths['sign-passphrase'].write_text('') keyring = make_keyring(paths['gnupghome']) fingerprint = keyring.import_key(SECRET_KEY_MATERIAL) unsigned_file = Path(self.fakes.fs.root.join('unsigned')) unsigned_file.write_text('some text to sign') detach_sign_file = Path(self.fakes.fs.root.join('signature')) detach_sign(fingerprint, unsigned_file, detach_sign_file, paths=paths) signature = detach_sign_file.read_text() self.assertTrue(signature.startswith('-----BEGIN PGP SIGNATURE-----')) self.assertTrue(signature.endswith('-----END PGP SIGNATURE-----\n'))
def test_inline_sign(self): """inline_sign creates an inline signature for a file.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) paths['gnupghome'].mkdir(parents=True) paths['sign-passphrase'].write_text('') keyring = make_keyring(paths['gnupghome']) fingerprint = keyring.import_key(SECRET_KEY_MATERIAL) unsigned_file = Path(self.fakes.fs.root.join('unsigned')) unsigned_file.write_text('some text to sign') inline_sign_file = Path(self.fakes.fs.root.join('signed')) inline_sign(fingerprint, unsigned_file, inline_sign_file, paths=paths) signed_content = inline_sign_file.read_text() self.assertIn('some text to sign', signed_content) self.assertIn('-----BEGIN PGP SIGNATURE-----', signed_content)
def get_virtualhost_config(auth_backends, resource_name, auth_cache_enabled, auth_cache_duration, auth_cache_inactivity, hookenv=hookenv): """Return the configuration for the static virtuahost.""" paths = get_paths() domain = get_virtualhost_name(hookenv=hookenv) return { 'domain': domain, 'document_root': str(paths['static']), 'auth_backends': auth_backends or [], 'resource_name': resource_name, 'auth_cache_enabled': auth_cache_enabled, 'auth_cache_duration': auth_cache_duration, 'auth_cache_inactivity': auth_cache_inactivity, 'basic_auth_file': str(paths['basic-auth']) }
def test_disable_mirroring(self): """disable_mirroring renames the script config file.""" paths = get_paths(root_dir=Path(self.fakes.fs.root.path)) uri = 'https://*****:*****@example.com/ubuntu xenial main universe' configure_reprepro(uri, 'i386 amd64', 'ABABABAB', 'CDCDCDCD', 'very secret', get_paths=lambda: paths) config = paths['config'] self.assertTrue(config.exists()) orig_content = config.read_text() disable_mirroring(get_paths=lambda: paths) self.assertFalse(config.exists()) # The file is moved to .disabled disabled_file = config.with_suffix('.disabled') self.assertTrue(disabled_file.exists()) self.assertEqual(orig_content, disabled_file.read_text())
def install_resources(root_dir=None): """Create tree structure and install resources from the charm.""" paths = get_paths(root_dir=root_dir) for name in ('bin', 'reprepro-conf', 'static'): host.mkdir(str(paths[name]), perms=0o755) # the gpg directory should only be readable by root host.mkdir(str(paths['gnupghome']), perms=0o700) # create an empty basic-auth password file. It will be updated by a script # run as root, but it must be readable by the web server host.write_file( str(paths['basic-auth']), b'', group='www-data', perms=0o640) # create an empty sign passphrase file, only readable by root host.write_file(str(paths['sign-passphrase']), b'', perms=0o600) # install scripts for script in SCRIPTS: create_script_file(script, paths['bin']) # symlink the lib libary to make it available to scripts too (paths['bin'] / 'lib').symlink_to(Path.cwd() / 'lib')
def test_get_paths(self): """get_paths returns service paths.""" paths = get_paths() self.assertEqual( { 'base': Path('/srv/archive-auth-mirror'), 'cron': Path('/etc/cron.d/archive-auth-mirror'), 'bin': Path('/srv/archive-auth-mirror/bin'), 'config': Path('/srv/archive-auth-mirror/config.yaml'), 'static': Path('/srv/archive-auth-mirror/static'), 'basic-auth': Path('/srv/archive-auth-mirror/basic-auth'), 'sign-passphrase': Path('/srv/archive-auth-mirror/sign-passphrase'), 'ssh-key': Path('/srv/archive-auth-mirror/ssh-key'), 'authorized-keys': Path('/root/.ssh/authorized_keys'), 'lockfile': Path('/srv/archive-auth-mirror/mirror-archive.lock'), 'reprepro': Path('/srv/archive-auth-mirror/reprepro'), 'reprepro-conf': Path('/srv/archive-auth-mirror/reprepro/conf'), 'gnupghome': Path('/srv/archive-auth-mirror/reprepro/.gnupg') }, paths)
def config_set(): config = hookenv.config() missing_options = setup.missing_options(config) if missing_options: hookenv.status_set( 'blocked', 'Mirroring is disabled as some configuration options are missing: ' '{}'.format(', '.join(missing_options))) return # Configure mirroring. keyring = gpg.KeyRing() mirrors = mirror.from_config(keyring, config['mirrors'], config['repository-origin'].strip()) sign_key_fingerprint = keyring.import_key(config['sign-gpg-key']) sign_key_passphrase = config.get('sign-gpg-passphrase', '').strip() repository.configure_reprepro(mirrors, sign_key_fingerprint, sign_key_passphrase) # Export the public key used to sign the repository. _export_sign_key(sign_key_fingerprint) hookenv.status_set('active', 'Mirroring configured') # Update scripts config. utils.update_config(config_path=utils.get_paths()['config'], packages_require_auth=config['packages-require-auth'])
def set_ssh_key(ssh_keys): public_key_path = str(utils.get_paths()['ssh-key']) + '.pub' with open(public_key_path, 'r') as public_key_file: public_key = public_key_file.read() ssh_keys.set_local_public_key(public_key)
def setUp(self): super().setUp() root_dir = Path(self.fakes.fs.root.path) (root_dir / 'etc/cron.d').mkdir(parents=True) self.paths = get_paths(root_dir=root_dir)
def create_ssh_key(): ssh.create_key(utils.get_paths()['ssh-key'])
def disable_mirroring(get_paths=get_paths): """Disable mirroring.""" config = get_paths()['config'] if config.exists(): config.replace(config.with_suffix('.disabled'))
def _export_sign_key(key_id): """Export the public key for the repo under the static serve.""" filename = utils.get_paths()['static'] / 'key.asc' gpg.export_public_key(key_id, filename)
def create_ssh_key(): path = utils.get_paths()['ssh-key'] if not path.exists(): # only_once doesn't protect the handler from running if the line in # source code changes (so it can run again in an upgrade-charm hook) ssh.create_key(path)