Codebase list python-castellan / 6e03a68
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> Moises Guimaraes de Medeiros authored 5 years ago Moisés Guimarães de Medeiros committed 5 years ago
7 changed file(s) with 256 addition(s) and 2 deletion(s). Raw diff Collapse all Expand all
2727 .stestr/
2828 .venv
2929 cover
30 vault_*
3031
3132 # Translations
3233 *.mo
0 # Licensed under the Apache License, Version 2.0 (the "License"); you may
1 # not use this file except in compliance with the License. You may obtain
2 # a copy of the License at
3 #
4 # http://www.apache.org/licenses/LICENSE-2.0
5 #
6 # Unless required by applicable law or agreed to in writing, software
7 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
9 # License for the specific language governing permissions and limitations
10 # under the License.
11
12 r"""
13 Castellan Oslo Config Driver
14 ----------------------------
15
16 This driver is an oslo.config backend driver implemented with Castellan. It
17 extends oslo.config's capabilities by enabling it to retrieve configuration
18 values from a secret manager behind Castellan.
19
20 The setup of a Castellan configuration source is as follow::
21
22 [DEFAULT]
23 config_source = castellan_config_group
24
25 [castellan_config_group]
26 driver = castellan
27 config_file = castellan.conf
28 mapping_file = mapping.conf
29
30 In the following sessions, you can find more information about this driver's
31 classes and its options.
32
33 The Driver Class
34 ================
35
36 .. autoclass:: CastellanConfigurationSourceDriver
37
38 The Configuration Source Class
39 ==============================
40
41 .. autoclass:: CastellanConfigurationSource
42
43 """
44 from castellan.common.exception import KeyManagerError
45 from castellan.common.exception import ManagedObjectNotFoundError
46 from castellan import key_manager
47
48 from oslo_config import cfg
49 from oslo_config import sources
50 from oslo_log import log
51
52 LOG = log.getLogger(__name__)
53
54
55 class CastellanConfigurationSourceDriver(sources.ConfigurationSourceDriver):
56 """A backend driver for configuration values served through castellan.
57
58 Required options:
59 - config_file: The castellan configuration file.
60
61 - mapping_file: A configuration/castellan_id mapping file. This file
62 creates connections between configuration options and
63 castellan ids. The group and option name remains the
64 same, while the value gets stored a secret manager behind
65 castellan and is replaced by its castellan id. The ids
66 will be used to fetch the values through castellan.
67 """
68
69 _castellan_driver_opts = [
70 cfg.StrOpt(
71 'config_file',
72 required=True,
73 sample_default='etc/castellan/castellan.conf',
74 help=('The path to a castellan configuration file.'),
75 ),
76 cfg.StrOpt(
77 'mapping_file',
78 required=True,
79 sample_default='etc/castellan/secrets_mapping.conf',
80 help=('The path to a configuration/castellan_id mapping file.'),
81 ),
82 ]
83
84 def list_options_for_discovery(self):
85 return self._castellan_driver_opts
86
87 def open_source_from_opt_group(self, conf, group_name):
88 conf.register_opts(self._castellan_driver_opts, group_name)
89
90 return CastellanConfigurationSource(
91 group_name,
92 conf[group_name].config_file,
93 conf[group_name].mapping_file)
94
95
96 class CastellanConfigurationSource(sources.ConfigurationSource):
97 """A configuration source for configuration values served through castellan.
98
99 :param config_file: The path to a castellan configuration file.
100
101 :param mapping_file: The path to a configuration/castellan_id mapping file.
102 """
103
104 def __init__(self, group_name, config_file, mapping_file):
105 conf = cfg.ConfigOpts()
106 conf(args=[], default_config_files=[config_file])
107
108 self._name = group_name
109 self._mngr = key_manager.API(conf)
110 self._mapping = {}
111
112 cfg.ConfigParser(mapping_file, self._mapping).parse()
113
114 def get(self, group_name, option_name, opt):
115 try:
116 group_name = group_name or "DEFAULT"
117
118 castellan_id = self._mapping[group_name][option_name][0]
119
120 return (self._mngr.get("ctx", castellan_id).get_encoded().decode(),
121 cfg.LocationInfo(cfg.Locations.user, castellan_id))
122
123 except KeyError:
124 # no mapping 'option = castellan_id'
125 LOG.info("option '[%s] %s' not present in '[%s] mapping_file'",
126 group_name, option_name, self._name)
127
128 except KeyManagerError:
129 # bad mapping 'option =' without a castellan_id
130 LOG.warning("missing castellan_id for option "
131 "'[%s] %s' in '[%s] mapping_file'",
132 group_name, option_name, self._name)
133
134 except ManagedObjectNotFoundError:
135 # good mapping, but unknown castellan_id by secret manager
136 LOG.warning("invalid castellan_id for option "
137 "'[%s] %s' in '[%s] mapping_file'",
138 group_name, option_name, self._name)
139
140 return (sources._NoValue, None)
0 # Licensed under the Apache License, Version 2.0 (the "License"); you may
1 # not use this file except in compliance with the License. You may obtain
2 # a copy of the License at
3 #
4 # http://www.apache.org/licenses/LICENSE-2.0
5 #
6 # Unless required by applicable law or agreed to in writing, software
7 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
9 # License for the specific language governing permissions and limitations
10 # under the License.
11
12 """
13 Functional test cases for the Castellan Oslo Config Driver.
14
15 Note: This requires local running instance of Vault.
16 """
17 import tempfile
18
19 from oslo_config import cfg
20 from oslo_config import fixture
21
22 from oslotest import base
23
24 from castellan import _config_driver
25 from castellan.common.objects import opaque_data
26 from castellan.tests.unit.key_manager import fake
27
28
29 class CastellanSourceTestCase(base.BaseTestCase):
30
31 def setUp(self):
32 super(CastellanSourceTestCase, self).setUp()
33 self.driver = _config_driver.CastellanConfigurationSourceDriver()
34 self.conf = cfg.ConfigOpts()
35 self.conf_fixture = self.useFixture(fixture.Config(self.conf))
36
37 def test_incomplete_driver(self):
38 # The group exists, but does not specify the
39 # required options for this driver.
40 self.conf_fixture.load_raw_values(
41 group='incomplete_driver',
42 driver='castellan',
43 )
44 source = self.conf._open_source_from_opt_group('incomplete_driver')
45
46 self.assertIsNone(source)
47 self.assertEqual(self.conf.incomplete_driver.driver, 'castellan')
48
49 def test_complete_driver(self):
50 self.conf_fixture.load_raw_values(
51 group='castellan_source',
52 driver='castellan',
53 config_file='config.conf',
54 mapping_file='mapping.conf',
55 )
56
57 with base.mock.patch.object(
58 _config_driver,
59 'CastellanConfigurationSource') as source_class:
60 self.driver.open_source_from_opt_group(
61 self.conf, 'castellan_source')
62
63 source_class.assert_called_once_with(
64 'castellan_source',
65 self.conf.castellan_source.config_file,
66 self.conf.castellan_source.mapping_file)
67
68 def test_fetch_secret(self):
69 # fake KeyManager populated with secret
70 km = fake.fake_api()
71 secret_id = km.store("fake_context",
72 opaque_data.OpaqueData(b"super_secret!"))
73
74 # driver config
75 config = "[key_manager]\nbackend=vault"
76 mapping = "[DEFAULT]\nmy_secret=" + secret_id
77
78 # creating temp files
79 with tempfile.NamedTemporaryFile() as config_file:
80 config_file.write(config.encode("utf-8"))
81 config_file.flush()
82
83 with tempfile.NamedTemporaryFile() as mapping_file:
84 mapping_file.write(mapping.encode("utf-8"))
85 mapping_file.flush()
86
87 self.conf_fixture.load_raw_values(
88 group='castellan_source',
89 driver='castellan',
90 config_file=config_file.name,
91 mapping_file=mapping_file.name,
92 )
93
94 source = self.driver.open_source_from_opt_group(
95 self.conf,
96 'castellan_source')
97
98 # replacing key_manager with fake one
99 source._mngr = km
100
101 # testing if the source is able to retrieve
102 # the secret value stored in the key_manager
103 # using the secret_id from the mapping file
104 self.assertEqual("super_secret!",
105 source.get("DEFAULT",
106 "my_secret",
107 cfg.StrOpt(""))[0])
3232 netifaces==0.10.4
3333 openstackdocstheme==1.18.1
3434 os-client-config==1.28.0
35 oslo.config==5.2.0
35 oslo.config==6.4.0
3636 oslo.context==2.19.2
3737 oslo.i18n==3.15.3
3838 oslo.log==3.36.0
55 Babel!=2.4.0,>=2.3.4 # BSD
66 cryptography>=2.1 # BSD/Apache-2.0
77 python-barbicanclient>=4.5.2 # Apache-2.0
8 oslo.config>=5.2.0 # Apache-2.0
8 oslo.config>=6.4.0 # Apache-2.0
99 oslo.context>=2.19.2 # Apache-2.0
1010 oslo.i18n>=3.15.3 # Apache-2.0
1111 oslo.log>=3.36.0 # Apache-2.0
2525 oslo.config.opts =
2626 castellan.tests.functional.config = castellan.tests.functional.config:list_opts
2727 castellan.config = castellan.options:list_opts
28
29 oslo.config.driver =
30 castellan = castellan._config_driver:CastellanConfigurationSourceDriver
2831
2932 castellan.drivers =
3033 barbican = castellan.key_manager.barbican_key_manager:BarbicanKeyManager
99 openstackdocstheme>=1.18.1 # Apache-2.0
1010 oslotest>=3.2.0 # Apache-2.0
1111 stestr>=2.0.0 # Apache-2.0
12 fixtures>=3.0.0 # Apache-2.0/BSD
1213 testscenarios>=0.4 # Apache-2.0/BSD
1314 testtools>=2.2.0 # MIT
1415 bandit>=1.1.0 # Apache-2.0