diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-12-05 02:09:05 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-12-05 02:09:05 +0000 |
commit | caede514613c896709b9d7ae72b62e8520402bec (patch) | |
tree | e3cb0469fb21c4d09781f7c90ccda24278208061 | |
parent | 026a17c9eb9d8ebad8c56f8d1b7946bd4694519e (diff) | |
parent | b17bbcdef9acfaf6e2b47671c9982d3378045961 (diff) | |
download | os-client-config-caede514613c896709b9d7ae72b62e8520402bec.tar.gz |
Merge "Add support for secure.yaml file for auth info"
-rw-r--r-- | README.rst | 28 | ||||
-rw-r--r-- | os_client_config/config.py | 33 | ||||
-rw-r--r-- | os_client_config/tests/base.py | 12 | ||||
-rw-r--r-- | os_client_config/tests/test_config.py | 21 | ||||
-rw-r--r-- | os_client_config/tests/test_environ.py | 14 |
5 files changed, 95 insertions, 13 deletions
@@ -145,6 +145,34 @@ as a result of a chosen plugin need to go into the auth dict. For password auth, this includes `auth_url`, `username` and `password` as well as anything related to domains, projects and trusts. +Splitting Secrets +----------------- + +In some scenarios, such as configuragtion managment controlled environments, +it might be eaiser to have secrets in one file and non-secrets in another. +This is fully supported via an optional file `secure.yaml` which follows all +the same location rules as `clouds.yaml`. It can contain anything you put +in `clouds.yaml` and will take precedence over anything in the `clouds.yaml` +file. + +:: + + # clouds.yaml + clouds: + internap: + profile: internap + auth: + username: api-55f9a00fb2619 + project_name: inap-17037 + regions: + - ams01 + - nyj01 + # secure.yaml + clouds: + internap: + auth: + password: XXXXXXXXXXXXXXXXX + SSL Settings ------------ diff --git a/os_client_config/config.py b/os_client_config/config.py index 19cfa35..833da11 100644 --- a/os_client_config/config.py +++ b/os_client_config/config.py @@ -48,6 +48,11 @@ CONFIG_FILES = [ for d in CONFIG_SEARCH_PATH for s in YAML_SUFFIXES + JSON_SUFFIXES ] +SECURE_FILES = [ + os.path.join(d, 'secure' + s) + for d in CONFIG_SEARCH_PATH + for s in YAML_SUFFIXES + JSON_SUFFIXES +] VENDOR_FILES = [ os.path.join(d, 'clouds-public' + s) for d in CONFIG_SEARCH_PATH @@ -99,6 +104,20 @@ def _get_os_environ(envvar_prefix=None): return ret +def _merge_clouds(old_dict, new_dict): + """Like dict.update, except handling nested dicts.""" + ret = old_dict.copy() + for (k, v) in new_dict.items(): + if isinstance(v, dict): + if k in ret: + ret[k] = _merge_clouds(ret[k], v) + else: + ret[k] = v.copy() + else: + ret[k] = v + return ret + + def _auth_update(old_dict, new_dict): """Like dict.update, except handling the nested dict called auth.""" for (k, v) in new_dict.items(): @@ -116,20 +135,29 @@ class OpenStackConfig(object): def __init__(self, config_files=None, vendor_files=None, override_defaults=None, force_ipv4=None, - envvar_prefix=None): + envvar_prefix=None, secure_files=None): self._config_files = config_files or CONFIG_FILES + self._secure_files = secure_files or SECURE_FILES self._vendor_files = vendor_files or VENDOR_FILES config_file_override = os.environ.pop('OS_CLIENT_CONFIG_FILE', None) if config_file_override: self._config_files.insert(0, config_file_override) + secure_file_override = os.environ.pop('OS_CLIENT_SECURE_FILE', None) + if secure_file_override: + self._secure_files.insert(0, secure_file_override) + self.defaults = defaults.get_defaults() if override_defaults: self.defaults.update(override_defaults) # First, use a config file if it exists where expected self.config_filename, self.cloud_config = self._load_config_file() + _, secure_config = self._load_secure_file() + if secure_config: + self.cloud_config = _merge_clouds( + self.cloud_config, secure_config) if not self.cloud_config: self.cloud_config = {'clouds': {}} @@ -217,6 +245,9 @@ class OpenStackConfig(object): def _load_config_file(self): return self._load_yaml_json_file(self._config_files) + def _load_secure_file(self): + return self._load_yaml_json_file(self._secure_files) + def _load_vendor_file(self): return self._load_yaml_json_file(self._vendor_files) diff --git a/os_client_config/tests/base.py b/os_client_config/tests/base.py index 3d94e25..33a868d 100644 --- a/os_client_config/tests/base.py +++ b/os_client_config/tests/base.py @@ -64,7 +64,6 @@ USER_CONF = { 'auth': { 'auth_url': 'http://example.com/v2', 'username': 'testuser', - 'password': 'testpass', 'project_name': 'testproject', }, 'region-name': 'test-region', @@ -112,6 +111,15 @@ USER_CONF = { } }, } +SECURE_CONF = { + 'clouds': { + '_test_cloud_no_vendor': { + 'auth': { + 'password': 'testpass', + }, + } + } +} NO_CONF = { 'cache': {'max_age': 1}, } @@ -135,6 +143,7 @@ class TestCase(base.BaseTestCase): tdir = self.useFixture(fixtures.TempDir()) conf['cache']['path'] = tdir.path self.cloud_yaml = _write_yaml(conf) + self.secure_yaml = _write_yaml(SECURE_CONF) self.vendor_yaml = _write_yaml(VENDOR_CONF) self.no_yaml = _write_yaml(NO_CONF) @@ -155,6 +164,7 @@ class TestCase(base.BaseTestCase): self.assertIsNone(cc.cloud) self.assertIn('username', cc.auth) self.assertEqual('testuser', cc.auth['username']) + self.assertEqual('testpass', cc.auth['password']) self.assertFalse(cc.config['image_api_use_tasks']) self.assertTrue('project_name' in cc.auth or 'project_id' in cc.auth) if 'project_name' in cc.auth: diff --git a/os_client_config/tests/test_config.py b/os_client_config/tests/test_config.py index d225b7c..aff8c6d 100644 --- a/os_client_config/tests/test_config.py +++ b/os_client_config/tests/test_config.py @@ -30,7 +30,8 @@ class TestConfig(base.TestCase): def test_get_all_clouds(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], - vendor_files=[self.vendor_yaml]) + vendor_files=[self.vendor_yaml], + secure_files=[self.no_yaml]) clouds = c.get_all_clouds() # We add one by hand because the regions cloud is going to exist # twice since it has two regions in it @@ -74,7 +75,8 @@ class TestConfig(base.TestCase): def test_get_one_cloud_with_config_files(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], - vendor_files=[self.vendor_yaml]) + vendor_files=[self.vendor_yaml], + secure_files=[self.secure_yaml]) self.assertIsInstance(c.cloud_config, dict) self.assertIn('cache', c.cloud_config) self.assertIsInstance(c.cloud_config['cache'], dict) @@ -129,7 +131,8 @@ class TestConfig(base.TestCase): def test_fallthrough(self): c = config.OpenStackConfig(config_files=[self.no_yaml], - vendor_files=[self.no_yaml]) + vendor_files=[self.no_yaml], + secure_files=[self.no_yaml]) for k in os.environ.keys(): if k.startswith('OS_'): self.useFixture(fixtures.EnvironmentVariable(k)) @@ -137,7 +140,8 @@ class TestConfig(base.TestCase): def test_prefer_ipv6_true(self): c = config.OpenStackConfig(config_files=[self.no_yaml], - vendor_files=[self.no_yaml]) + vendor_files=[self.no_yaml], + secure_files=[self.no_yaml]) cc = c.get_one_cloud(cloud='defaults', validate=False) self.assertTrue(cc.prefer_ipv6) @@ -155,7 +159,8 @@ class TestConfig(base.TestCase): def test_force_ipv4_false(self): c = config.OpenStackConfig(config_files=[self.no_yaml], - vendor_files=[self.no_yaml]) + vendor_files=[self.no_yaml], + secure_files=[self.no_yaml]) cc = c.get_one_cloud(cloud='defaults', validate=False) self.assertFalse(cc.force_ipv4) @@ -166,7 +171,8 @@ class TestConfig(base.TestCase): self.assertEqual('testpass', cc.auth['password']) def test_get_cloud_names(self): - c = config.OpenStackConfig(config_files=[self.cloud_yaml]) + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + secure_files=[self.no_yaml]) self.assertEqual( ['_test-cloud-domain-id_', '_test-cloud-int-project_', @@ -177,7 +183,8 @@ class TestConfig(base.TestCase): ], sorted(c.get_cloud_names())) c = config.OpenStackConfig(config_files=[self.no_yaml], - vendor_files=[self.no_yaml]) + vendor_files=[self.no_yaml], + secure_files=[self.no_yaml]) for k in os.environ.keys(): if k.startswith('OS_'): self.useFixture(fixtures.EnvironmentVariable(k)) diff --git a/os_client_config/tests/test_environ.py b/os_client_config/tests/test_environ.py index 0ff800f..b75db1c 100644 --- a/os_client_config/tests/test_environ.py +++ b/os_client_config/tests/test_environ.py @@ -30,6 +30,8 @@ class TestEnviron(base.TestCase): self.useFixture( fixtures.EnvironmentVariable('OS_USERNAME', 'testuser')) self.useFixture( + fixtures.EnvironmentVariable('OS_PASSWORD', 'testpass')) + self.useFixture( fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject')) self.useFixture( fixtures.EnvironmentVariable('NOVA_PROJECT_ID', 'testnova')) @@ -57,13 +59,15 @@ class TestEnviron(base.TestCase): self.useFixture( fixtures.EnvironmentVariable('OS_PREFER_IPV6', 'false')) c = config.OpenStackConfig(config_files=[self.cloud_yaml], - vendor_files=[self.vendor_yaml]) + vendor_files=[self.vendor_yaml], + secure_files=[self.secure_yaml]) cc = c.get_one_cloud('_test-cloud_') self.assertFalse(cc.prefer_ipv6) def test_environ_exists(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], - vendor_files=[self.vendor_yaml]) + vendor_files=[self.vendor_yaml], + secure_files=[self.secure_yaml]) cc = c.get_one_cloud('envvars') self._assert_cloud_details(cc) self.assertNotIn('auth_url', cc.config) @@ -78,7 +82,8 @@ class TestEnviron(base.TestCase): def test_environ_prefix(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], vendor_files=[self.vendor_yaml], - envvar_prefix='NOVA_') + envvar_prefix='NOVA_', + secure_files=[self.secure_yaml]) cc = c.get_one_cloud('envvars') self._assert_cloud_details(cc) self.assertNotIn('auth_url', cc.config) @@ -92,7 +97,8 @@ class TestEnviron(base.TestCase): def test_get_one_cloud_with_config_files(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], - vendor_files=[self.vendor_yaml]) + vendor_files=[self.vendor_yaml], + secure_files=[self.secure_yaml]) self.assertIsInstance(c.cloud_config, dict) self.assertIn('cache', c.cloud_config) self.assertIsInstance(c.cloud_config['cache'], dict) |