summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoises Guimaraes de Medeiros <moguimar@redhat.com>2018-09-04 14:54:01 +0200
committerMoisés Guimarães de Medeiros <moguimar@redhat.com>2019-01-09 23:17:17 +0100
commit6e03a68c14e168c834f95b0fa50f1c427ae38abe (patch)
tree738b959ed1cc457d8e9dc195f5e4e63da7e6d001
parent0450c738193cd7ee6f4b511e424fa0849bb7722b (diff)
Add Castellan Oslo Config Driver.HEAD1.1.0master
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>
Notes
Notes (review): Code-Review+1: Raildo Mascena <rmascena@redhat.com> Code-Review+2: Juan Antonio Osorio Robles <jaosorior@redhat.com> Code-Review+1: Gage Hugo <gagehugo@gmail.com> Code-Review+2: Ben Nemec <openstack@nemebean.com> Workflow+1: Ben Nemec <openstack@nemebean.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Fri, 11 Jan 2019 16:07:55 +0000 Reviewed-on: https://review.openstack.org/599589 Project: openstack/castellan Branch: refs/heads/master
-rw-r--r--.gitignore1
-rw-r--r--castellan/_config_driver.py141
-rw-r--r--castellan/tests/unit/test_config_driver.py108
-rw-r--r--lower-constraints.txt2
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg3
-rw-r--r--test-requirements.txt1
7 files changed, 256 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 3edc8ef..304efbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ pip-log.txt
28.stestr/ 28.stestr/
29.venv 29.venv
30cover 30cover
31vault_*
31 32
32# Translations 33# Translations
33*.mo 34*.mo
diff --git a/castellan/_config_driver.py b/castellan/_config_driver.py
new file mode 100644
index 0000000..26fdb0a
--- /dev/null
+++ b/castellan/_config_driver.py
@@ -0,0 +1,141 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13r"""
14Castellan Oslo Config Driver
15----------------------------
16
17This driver is an oslo.config backend driver implemented with Castellan. It
18extends oslo.config's capabilities by enabling it to retrieve configuration
19values from a secret manager behind Castellan.
20
21The setup of a Castellan configuration source is as follow::
22
23 [DEFAULT]
24 config_source = castellan_config_group
25
26 [castellan_config_group]
27 driver = castellan
28 config_file = castellan.conf
29 mapping_file = mapping.conf
30
31In the following sessions, you can find more information about this driver's
32classes and its options.
33
34The Driver Class
35================
36
37.. autoclass:: CastellanConfigurationSourceDriver
38
39The Configuration Source Class
40==============================
41
42.. autoclass:: CastellanConfigurationSource
43
44"""
45from castellan.common.exception import KeyManagerError
46from castellan.common.exception import ManagedObjectNotFoundError
47from castellan import key_manager
48
49from oslo_config import cfg
50from oslo_config import sources
51from oslo_log import log
52
53LOG = log.getLogger(__name__)
54
55
56class CastellanConfigurationSourceDriver(sources.ConfigurationSourceDriver):
57 """A backend driver for configuration values served through castellan.
58
59 Required options:
60 - config_file: The castellan configuration file.
61
62 - mapping_file: A configuration/castellan_id mapping file. This file
63 creates connections between configuration options and
64 castellan ids. The group and option name remains the
65 same, while the value gets stored a secret manager behind
66 castellan and is replaced by its castellan id. The ids
67 will be used to fetch the values through castellan.
68 """
69
70 _castellan_driver_opts = [
71 cfg.StrOpt(
72 'config_file',
73 required=True,
74 sample_default='etc/castellan/castellan.conf',
75 help=('The path to a castellan configuration file.'),
76 ),
77 cfg.StrOpt(
78 'mapping_file',
79 required=True,
80 sample_default='etc/castellan/secrets_mapping.conf',
81 help=('The path to a configuration/castellan_id mapping file.'),
82 ),
83 ]
84
85 def list_options_for_discovery(self):
86 return self._castellan_driver_opts
87
88 def open_source_from_opt_group(self, conf, group_name):
89 conf.register_opts(self._castellan_driver_opts, group_name)
90
91 return CastellanConfigurationSource(
92 group_name,
93 conf[group_name].config_file,
94 conf[group_name].mapping_file)
95
96
97class CastellanConfigurationSource(sources.ConfigurationSource):
98 """A configuration source for configuration values served through castellan.
99
100 :param config_file: The path to a castellan configuration file.
101
102 :param mapping_file: The path to a configuration/castellan_id mapping file.
103 """
104
105 def __init__(self, group_name, config_file, mapping_file):
106 conf = cfg.ConfigOpts()
107 conf(args=[], default_config_files=[config_file])
108
109 self._name = group_name
110 self._mngr = key_manager.API(conf)
111 self._mapping = {}
112
113 cfg.ConfigParser(mapping_file, self._mapping).parse()
114
115 def get(self, group_name, option_name, opt):
116 try:
117 group_name = group_name or "DEFAULT"
118
119 castellan_id = self._mapping[group_name][option_name][0]
120
121 return (self._mngr.get("ctx", castellan_id).get_encoded().decode(),
122 cfg.LocationInfo(cfg.Locations.user, castellan_id))
123
124 except KeyError:
125 # no mapping 'option = castellan_id'
126 LOG.info("option '[%s] %s' not present in '[%s] mapping_file'",
127 group_name, option_name, self._name)
128
129 except KeyManagerError:
130 # bad mapping 'option =' without a castellan_id
131 LOG.warning("missing castellan_id for option "
132 "'[%s] %s' in '[%s] mapping_file'",
133 group_name, option_name, self._name)
134
135 except ManagedObjectNotFoundError:
136 # good mapping, but unknown castellan_id by secret manager
137 LOG.warning("invalid castellan_id for option "
138 "'[%s] %s' in '[%s] mapping_file'",
139 group_name, option_name, self._name)
140
141 return (sources._NoValue, None)
diff --git a/castellan/tests/unit/test_config_driver.py b/castellan/tests/unit/test_config_driver.py
new file mode 100644
index 0000000..5b3275d
--- /dev/null
+++ b/castellan/tests/unit/test_config_driver.py
@@ -0,0 +1,108 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13"""
14Functional test cases for the Castellan Oslo Config Driver.
15
16Note: This requires local running instance of Vault.
17"""
18import tempfile
19
20from oslo_config import cfg
21from oslo_config import fixture
22
23from oslotest import base
24
25from castellan import _config_driver
26from castellan.common.objects import opaque_data
27from castellan.tests.unit.key_manager import fake
28
29
30class CastellanSourceTestCase(base.BaseTestCase):
31
32 def setUp(self):
33 super(CastellanSourceTestCase, self).setUp()
34 self.driver = _config_driver.CastellanConfigurationSourceDriver()
35 self.conf = cfg.ConfigOpts()
36 self.conf_fixture = self.useFixture(fixture.Config(self.conf))
37
38 def test_incomplete_driver(self):
39 # The group exists, but does not specify the
40 # required options for this driver.
41 self.conf_fixture.load_raw_values(
42 group='incomplete_driver',
43 driver='castellan',
44 )
45 source = self.conf._open_source_from_opt_group('incomplete_driver')
46
47 self.assertIsNone(source)
48 self.assertEqual(self.conf.incomplete_driver.driver, 'castellan')
49
50 def test_complete_driver(self):
51 self.conf_fixture.load_raw_values(
52 group='castellan_source',
53 driver='castellan',
54 config_file='config.conf',
55 mapping_file='mapping.conf',
56 )
57
58 with base.mock.patch.object(
59 _config_driver,
60 'CastellanConfigurationSource') as source_class:
61 self.driver.open_source_from_opt_group(
62 self.conf, 'castellan_source')
63
64 source_class.assert_called_once_with(
65 'castellan_source',
66 self.conf.castellan_source.config_file,
67 self.conf.castellan_source.mapping_file)
68
69 def test_fetch_secret(self):
70 # fake KeyManager populated with secret
71 km = fake.fake_api()
72 secret_id = km.store("fake_context",
73 opaque_data.OpaqueData(b"super_secret!"))
74
75 # driver config
76 config = "[key_manager]\nbackend=vault"
77 mapping = "[DEFAULT]\nmy_secret=" + secret_id
78
79 # creating temp files
80 with tempfile.NamedTemporaryFile() as config_file:
81 config_file.write(config.encode("utf-8"))
82 config_file.flush()
83
84 with tempfile.NamedTemporaryFile() as mapping_file:
85 mapping_file.write(mapping.encode("utf-8"))
86 mapping_file.flush()
87
88 self.conf_fixture.load_raw_values(
89 group='castellan_source',
90 driver='castellan',
91 config_file=config_file.name,
92 mapping_file=mapping_file.name,
93 )
94
95 source = self.driver.open_source_from_opt_group(
96 self.conf,
97 'castellan_source')
98
99 # replacing key_manager with fake one
100 source._mngr = km
101
102 # testing if the source is able to retrieve
103 # the secret value stored in the key_manager
104 # using the secret_id from the mapping file
105 self.assertEqual("super_secret!",
106 source.get("DEFAULT",
107 "my_secret",
108 cfg.StrOpt(""))[0])
diff --git a/lower-constraints.txt b/lower-constraints.txt
index b51623b..3432ac9 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -33,7 +33,7 @@ netaddr==0.7.18
33netifaces==0.10.4 33netifaces==0.10.4
34openstackdocstheme==1.18.1 34openstackdocstheme==1.18.1
35os-client-config==1.28.0 35os-client-config==1.28.0
36oslo.config==5.2.0 36oslo.config==6.4.0
37oslo.context==2.19.2 37oslo.context==2.19.2
38oslo.i18n==3.15.3 38oslo.i18n==3.15.3
39oslo.log==3.36.0 39oslo.log==3.36.0
diff --git a/requirements.txt b/requirements.txt
index a6020f7..eeeb417 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
6Babel!=2.4.0,>=2.3.4 # BSD 6Babel!=2.4.0,>=2.3.4 # BSD
7cryptography>=2.1 # BSD/Apache-2.0 7cryptography>=2.1 # BSD/Apache-2.0
8python-barbicanclient>=4.5.2 # Apache-2.0 8python-barbicanclient>=4.5.2 # Apache-2.0
9oslo.config>=5.2.0 # Apache-2.0 9oslo.config>=6.4.0 # Apache-2.0
10oslo.context>=2.19.2 # Apache-2.0 10oslo.context>=2.19.2 # Apache-2.0
11oslo.i18n>=3.15.3 # Apache-2.0 11oslo.i18n>=3.15.3 # Apache-2.0
12oslo.log>=3.36.0 # Apache-2.0 12oslo.log>=3.36.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 993a292..63bacbe 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,6 +27,9 @@ oslo.config.opts =
27 castellan.tests.functional.config = castellan.tests.functional.config:list_opts 27 castellan.tests.functional.config = castellan.tests.functional.config:list_opts
28 castellan.config = castellan.options:list_opts 28 castellan.config = castellan.options:list_opts
29 29
30oslo.config.driver =
31 castellan = castellan._config_driver:CastellanConfigurationSourceDriver
32
30castellan.drivers = 33castellan.drivers =
31 barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager 34 barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager
32 vault = castellan.key_manager.vault_key_manager:VaultKeyManager 35 vault = castellan.key_manager.vault_key_manager:VaultKeyManager
diff --git a/test-requirements.txt b/test-requirements.txt
index b35c1cb..918095d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -10,6 +10,7 @@ sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
10openstackdocstheme>=1.18.1 # Apache-2.0 10openstackdocstheme>=1.18.1 # Apache-2.0
11oslotest>=3.2.0 # Apache-2.0 11oslotest>=3.2.0 # Apache-2.0
12stestr>=2.0.0 # Apache-2.0 12stestr>=2.0.0 # Apache-2.0
13fixtures>=3.0.0 # Apache-2.0/BSD
13testscenarios>=0.4 # Apache-2.0/BSD 14testscenarios>=0.4 # Apache-2.0/BSD
14testtools>=2.2.0 # MIT 15testtools>=2.2.0 # MIT
15bandit>=1.1.0 # Apache-2.0 16bandit>=1.1.0 # Apache-2.0