Add Castellan Oslo Config Driver.
This driver is an oslo.config backend driver implemented with Castellan. It extends oslo.config's capabilities by enabling it to retrieve configuration values from a secret manager behind Castellan. Change-Id: Id7cf99bea5788e0a6309461a75eaa8d08d29641b Signed-off-by: Moises Guimaraes de Medeiros <moguimar@redhat.com>
This commit is contained in:
parent
0450c73819
commit
6e03a68c14
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,6 +28,7 @@ pip-log.txt
|
||||
.stestr/
|
||||
.venv
|
||||
cover
|
||||
vault_*
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
141
castellan/_config_driver.py
Normal file
141
castellan/_config_driver.py
Normal file
@ -0,0 +1,141 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
r"""
|
||||
Castellan Oslo Config Driver
|
||||
----------------------------
|
||||
|
||||
This driver is an oslo.config backend driver implemented with Castellan. It
|
||||
extends oslo.config's capabilities by enabling it to retrieve configuration
|
||||
values from a secret manager behind Castellan.
|
||||
|
||||
The setup of a Castellan configuration source is as follow::
|
||||
|
||||
[DEFAULT]
|
||||
config_source = castellan_config_group
|
||||
|
||||
[castellan_config_group]
|
||||
driver = castellan
|
||||
config_file = castellan.conf
|
||||
mapping_file = mapping.conf
|
||||
|
||||
In the following sessions, you can find more information about this driver's
|
||||
classes and its options.
|
||||
|
||||
The Driver Class
|
||||
================
|
||||
|
||||
.. autoclass:: CastellanConfigurationSourceDriver
|
||||
|
||||
The Configuration Source Class
|
||||
==============================
|
||||
|
||||
.. autoclass:: CastellanConfigurationSource
|
||||
|
||||
"""
|
||||
from castellan.common.exception import KeyManagerError
|
||||
from castellan.common.exception import ManagedObjectNotFoundError
|
||||
from castellan import key_manager
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import sources
|
||||
from oslo_log import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class CastellanConfigurationSourceDriver(sources.ConfigurationSourceDriver):
|
||||
"""A backend driver for configuration values served through castellan.
|
||||
|
||||
Required options:
|
||||
- config_file: The castellan configuration file.
|
||||
|
||||
- mapping_file: A configuration/castellan_id mapping file. This file
|
||||
creates connections between configuration options and
|
||||
castellan ids. The group and option name remains the
|
||||
same, while the value gets stored a secret manager behind
|
||||
castellan and is replaced by its castellan id. The ids
|
||||
will be used to fetch the values through castellan.
|
||||
"""
|
||||
|
||||
_castellan_driver_opts = [
|
||||
cfg.StrOpt(
|
||||
'config_file',
|
||||
required=True,
|
||||
sample_default='etc/castellan/castellan.conf',
|
||||
help=('The path to a castellan configuration file.'),
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'mapping_file',
|
||||
required=True,
|
||||
sample_default='etc/castellan/secrets_mapping.conf',
|
||||
help=('The path to a configuration/castellan_id mapping file.'),
|
||||
),
|
||||
]
|
||||
|
||||
def list_options_for_discovery(self):
|
||||
return self._castellan_driver_opts
|
||||
|
||||
def open_source_from_opt_group(self, conf, group_name):
|
||||
conf.register_opts(self._castellan_driver_opts, group_name)
|
||||
|
||||
return CastellanConfigurationSource(
|
||||
group_name,
|
||||
conf[group_name].config_file,
|
||||
conf[group_name].mapping_file)
|
||||
|
||||
|
||||
class CastellanConfigurationSource(sources.ConfigurationSource):
|
||||
"""A configuration source for configuration values served through castellan.
|
||||
|
||||
:param config_file: The path to a castellan configuration file.
|
||||
|
||||
:param mapping_file: The path to a configuration/castellan_id mapping file.
|
||||
"""
|
||||
|
||||
def __init__(self, group_name, config_file, mapping_file):
|
||||
conf = cfg.ConfigOpts()
|
||||
conf(args=[], default_config_files=[config_file])
|
||||
|
||||
self._name = group_name
|
||||
self._mngr = key_manager.API(conf)
|
||||
self._mapping = {}
|
||||
|
||||
cfg.ConfigParser(mapping_file, self._mapping).parse()
|
||||
|
||||
def get(self, group_name, option_name, opt):
|
||||
try:
|
||||
group_name = group_name or "DEFAULT"
|
||||
|
||||
castellan_id = self._mapping[group_name][option_name][0]
|
||||
|
||||
return (self._mngr.get("ctx", castellan_id).get_encoded().decode(),
|
||||
cfg.LocationInfo(cfg.Locations.user, castellan_id))
|
||||
|
||||
except KeyError:
|
||||
# no mapping 'option = castellan_id'
|
||||
LOG.info("option '[%s] %s' not present in '[%s] mapping_file'",
|
||||
group_name, option_name, self._name)
|
||||
|
||||
except KeyManagerError:
|
||||
# bad mapping 'option =' without a castellan_id
|
||||
LOG.warning("missing castellan_id for option "
|
||||
"'[%s] %s' in '[%s] mapping_file'",
|
||||
group_name, option_name, self._name)
|
||||
|
||||
except ManagedObjectNotFoundError:
|
||||
# good mapping, but unknown castellan_id by secret manager
|
||||
LOG.warning("invalid castellan_id for option "
|
||||
"'[%s] %s' in '[%s] mapping_file'",
|
||||
group_name, option_name, self._name)
|
||||
|
||||
return (sources._NoValue, None)
|
108
castellan/tests/unit/test_config_driver.py
Normal file
108
castellan/tests/unit/test_config_driver.py
Normal file
@ -0,0 +1,108 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Functional test cases for the Castellan Oslo Config Driver.
|
||||
|
||||
Note: This requires local running instance of Vault.
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture
|
||||
|
||||
from oslotest import base
|
||||
|
||||
from castellan import _config_driver
|
||||
from castellan.common.objects import opaque_data
|
||||
from castellan.tests.unit.key_manager import fake
|
||||
|
||||
|
||||
class CastellanSourceTestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CastellanSourceTestCase, self).setUp()
|
||||
self.driver = _config_driver.CastellanConfigurationSourceDriver()
|
||||
self.conf = cfg.ConfigOpts()
|
||||
self.conf_fixture = self.useFixture(fixture.Config(self.conf))
|
||||
|
||||
def test_incomplete_driver(self):
|
||||
# The group exists, but does not specify the
|
||||
# required options for this driver.
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='incomplete_driver',
|
||||
driver='castellan',
|
||||
)
|
||||
source = self.conf._open_source_from_opt_group('incomplete_driver')
|
||||
|
||||
self.assertIsNone(source)
|
||||
self.assertEqual(self.conf.incomplete_driver.driver, 'castellan')
|
||||
|
||||
def test_complete_driver(self):
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='castellan_source',
|
||||
driver='castellan',
|
||||
config_file='config.conf',
|
||||
mapping_file='mapping.conf',
|
||||
)
|
||||
|
||||
with base.mock.patch.object(
|
||||
_config_driver,
|
||||
'CastellanConfigurationSource') as source_class:
|
||||
self.driver.open_source_from_opt_group(
|
||||
self.conf, 'castellan_source')
|
||||
|
||||
source_class.assert_called_once_with(
|
||||
'castellan_source',
|
||||
self.conf.castellan_source.config_file,
|
||||
self.conf.castellan_source.mapping_file)
|
||||
|
||||
def test_fetch_secret(self):
|
||||
# fake KeyManager populated with secret
|
||||
km = fake.fake_api()
|
||||
secret_id = km.store("fake_context",
|
||||
opaque_data.OpaqueData(b"super_secret!"))
|
||||
|
||||
# driver config
|
||||
config = "[key_manager]\nbackend=vault"
|
||||
mapping = "[DEFAULT]\nmy_secret=" + secret_id
|
||||
|
||||
# creating temp files
|
||||
with tempfile.NamedTemporaryFile() as config_file:
|
||||
config_file.write(config.encode("utf-8"))
|
||||
config_file.flush()
|
||||
|
||||
with tempfile.NamedTemporaryFile() as mapping_file:
|
||||
mapping_file.write(mapping.encode("utf-8"))
|
||||
mapping_file.flush()
|
||||
|
||||
self.conf_fixture.load_raw_values(
|
||||
group='castellan_source',
|
||||
driver='castellan',
|
||||
config_file=config_file.name,
|
||||
mapping_file=mapping_file.name,
|
||||
)
|
||||
|
||||
source = self.driver.open_source_from_opt_group(
|
||||
self.conf,
|
||||
'castellan_source')
|
||||
|
||||
# replacing key_manager with fake one
|
||||
source._mngr = km
|
||||
|
||||
# testing if the source is able to retrieve
|
||||
# the secret value stored in the key_manager
|
||||
# using the secret_id from the mapping file
|
||||
self.assertEqual("super_secret!",
|
||||
source.get("DEFAULT",
|
||||
"my_secret",
|
||||
cfg.StrOpt(""))[0])
|
@ -33,7 +33,7 @@ netaddr==0.7.18
|
||||
netifaces==0.10.4
|
||||
openstackdocstheme==1.18.1
|
||||
os-client-config==1.28.0
|
||||
oslo.config==5.2.0
|
||||
oslo.config==6.4.0
|
||||
oslo.context==2.19.2
|
||||
oslo.i18n==3.15.3
|
||||
oslo.log==3.36.0
|
||||
|
@ -6,7 +6,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
Babel!=2.4.0,>=2.3.4 # BSD
|
||||
cryptography>=2.1 # BSD/Apache-2.0
|
||||
python-barbicanclient>=4.5.2 # Apache-2.0
|
||||
oslo.config>=5.2.0 # Apache-2.0
|
||||
oslo.config>=6.4.0 # Apache-2.0
|
||||
oslo.context>=2.19.2 # Apache-2.0
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
|
@ -27,6 +27,9 @@ oslo.config.opts =
|
||||
castellan.tests.functional.config = castellan.tests.functional.config:list_opts
|
||||
castellan.config = castellan.options:list_opts
|
||||
|
||||
oslo.config.driver =
|
||||
castellan = castellan._config_driver:CastellanConfigurationSourceDriver
|
||||
|
||||
castellan.drivers =
|
||||
barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager
|
||||
vault = castellan.key_manager.vault_key_manager:VaultKeyManager
|
||||
|
@ -10,6 +10,7 @@ sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
stestr>=2.0.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
bandit>=1.1.0 # Apache-2.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user