Make the GPG validation keyring configurable

Add a new configuration option custom_keyring which allows the user to
provide a custom GPG keyring for validating the simplestreams source,
instead of the default /usr/share/keyrings/ubuntu-cloudimage-keyring.gpg

Change-Id: Ib564a6e259274a076675687a03c0c2030d35e221
This commit is contained in:
Trent Lloyd 2024-10-01 12:50:03 +00:00
parent cd1eaa4e96
commit c9db7fe9a9
5 changed files with 67 additions and 6 deletions

View File

@ -144,3 +144,15 @@ options:
description: |
Enable conversion of images to raw format when uploading images to Glance.
Only supported when "image_conversion" is enabled in Glance.
custom_keyring:
type: string
default:
description: |
base64-encoded GPG keyring for verifying the simplestreams mirror
signature. Only required when mirroring a custom user-created repository,
defaults to /usr/share/keyrings/ubuntu-cloudimage-keyring.gpg which is
used to sign cloud-images.ubuntu.com.
Can be produced with the following command:
gpg --no-default-keyring --keyring gnupg-ring:custom_keyring.gpg \
--keyserver keyserver.ubuntu.com --recv-keys KEYID

View File

@ -69,7 +69,7 @@ def setup_file_logging():
log = logging.getLogger()
KEYRING = '/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg'
DEFAULT_KEYRING = '/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg'
CONF_FILE_DIR = '/etc/glance-simplestreams-sync'
PID_FILE_DIR = '/var/run'
CHARM_CONF_FILE_NAME = os.path.join(CONF_FILE_DIR, 'mirrors.yaml')
@ -271,7 +271,7 @@ def do_sync(ksc, charm_conf):
"--cloud-name", charm_conf['cloud_name'],
"--path", mirror_info['path'],
"--name-prefix", charm_conf['name_prefix'],
"--keyring", KEYRING,
"--keyring", charm_conf.get('keyring_path', DEFAULT_KEYRING),
"--log-file", SSTREAM_LOG_FILE,
]

View File

@ -61,6 +61,7 @@ from charmhelpers.core.host import (
CompareHostReleases,
lsb_release,
install_ca_cert,
write_file,
)
CONF_FILE_DIR = '/etc/glance-simplestreams-sync'
@ -69,6 +70,9 @@ USR_SHARE_DIR = '/usr/share/glance-simplestreams-sync'
MIRRORS_CONF_FILE_NAME = os.path.join(CONF_FILE_DIR, 'mirrors.yaml')
ID_CONF_FILE_NAME = os.path.join(CONF_FILE_DIR, 'identity.yaml')
SSTREAM_SNAP_COMMON = '/var/snap/simplestreams/common'
CUSTOM_KEYRING_PATH = os.path.join(SSTREAM_SNAP_COMMON, 'custom_keyring.gpg')
SYNC_SCRIPT_NAME = "glance_simplestreams_sync.py"
SCRIPT_WRAPPER_NAME = "glance-simplestreams-sync.sh"
@ -170,6 +174,11 @@ class MirrorsConfigServiceContext(OSContextGenerator):
if len(modify_hook_scripts) == 0:
modify_hook_scripts.append('/bin/true')
if config.get('custom_keyring'):
keyring_path = CUSTOM_KEYRING_PATH
else:
keyring_path = '/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg'
return dict(mirror_list=config['mirror_list'],
modify_hook_scripts=', '.join(modify_hook_scripts),
name_prefix=config['name_prefix'],
@ -184,7 +193,8 @@ class MirrorsConfigServiceContext(OSContextGenerator):
custom_properties=config['custom_properties'],
hypervisor_mapping=config['hypervisor_mapping'],
set_latest_property=config['set_latest_property'],
image_import_conversion=config['image_import_conversion'])
image_import_conversion=config['image_import_conversion'],
keyring_path=keyring_path)
def ensure_perms():
@ -360,6 +370,13 @@ def config_changed():
base64.b64decode(config.get('ssl_ca')),
)
keyring = config.get('custom_keyring')
if keyring:
decoded = base64.b64decode(keyring)
write_file(CUSTOM_KEYRING_PATH, decoded)
elif os.path.exists(CUSTOM_KEYRING_PATH):
os.remove(CUSTOM_KEYRING_PATH)
config.save()

View File

@ -12,7 +12,8 @@ hypervisor_mapping: {{ hypervisor_mapping }}
image_import_conversion: {{ image_import_conversion }}
{% if custom_properties %}
custom_properties: {{ custom_properties }}
{% endif %}
{% endif -%}
{% if set_latest_property %}
set_latest_property: {{ set_latest_property }}
{% endif %}
{% endif -%}
keyring_path: {{ keyring_path }}

View File

@ -42,6 +42,8 @@ class TestConfigChanged(CharmTestCase):
shutil.rmtree(self.tmpcrond)
shutil.rmtree(self.sharedir)
@mock.patch('os.remove')
@mock.patch.object(hooks, 'write_file')
@mock.patch.object(hooks, 'update_nrpe_config')
@mock.patch('os.symlink')
@mock.patch('charmhelpers.core.hookenv.config')
@ -51,7 +53,7 @@ class TestConfigChanged(CharmTestCase):
@mock.patch('charmhelpers.contrib.charmsupport.nrpe.local_unit')
def test_default_config(self, local_unit, nrpe_config, nag_host,
relations_of_type, config, symlink,
update_nrpe_config):
update_nrpe_config, write_file, os_remove):
local_unit.return_value = 'juju/0'
nag_host.return_value = "nagios_hostname"
nrpe_config.return_value = self.test_config
@ -75,6 +77,35 @@ class TestConfigChanged(CharmTestCase):
update_nrpe_config.assert_called()
self.install_ca_cert.assert_called_with(b'foobar')
write_file.assert_not_called()
os_remove.assert_not_called()
@mock.patch('os.remove')
@mock.patch.object(hooks, 'write_file')
@mock.patch.object(hooks, 'update_nrpe_config')
@mock.patch('os.symlink')
@mock.patch('charmhelpers.core.hookenv.config')
@mock.patch('charmhelpers.core.hookenv.relations_of_type')
@mock.patch('charmhelpers.contrib.charmsupport.nrpe.get_nagios_hostname')
@mock.patch('charmhelpers.contrib.charmsupport.nrpe.config')
@mock.patch('charmhelpers.contrib.charmsupport.nrpe.local_unit')
def test_custom_keyring(self, local_unit, nrpe_config, nag_host,
relations_of_type, config, symlink,
update_nrpe_config, write_file, os_remove):
local_unit.return_value = 'juju/0'
nag_host.return_value = "nagios_hostname"
nrpe_config.return_value = self.test_config
setattr(self.test_config, "changed", lambda x: False)
config.return_value = self.test_config
self.test_config.set('run', True)
self.test_config.set('ssl_ca', base64.b64encode(b'foobar'))
self.test_config.set('custom_keyring', 'dGVzdAo=')
hooks.config_changed()
write_file.assert_called_with(hooks.CUSTOM_KEYRING_PATH, b'test\n')
os_remove.assert_not_called()
@mock.patch.object(hooks, 'update_nrpe_config')
@mock.patch('os.path.exists')
@mock.patch('os.remove')