Skip to content

Commit 62c52f5

Browse files
RyanKorppiyakk2
authored andcommitted
config: Also mask non-prefix config
The 'config show' command will show information about your current configuration. When using a 'cloud.yaml' file and the 'OS_CLOUD' environment variable, the output of this will look like so: $ openstack config show +---------------------------------------------+----------------------------------+ | Field | Value | +---------------------------------------------+----------------------------------+ | additional_user_agent | [('osc-lib', '2.6.0')] | | api_timeout | None | | auth.auth_url | https://example.com:13000 | | auth.password | <redacted> | | auth.project_domain_id | default | | auth.project_id | c73b7097d07c46f78eb4b4dcfbac5ca8 | | auth.project_name | test-project | | auth.user_domain_name | example.com | | auth.username | john-doe | ... All of the 'auth.'-prefixed values are extracted from the corresponding entry in the 'clouds.yaml' file. You'll note that the 'auth.password' value is not shown. Instead, it is masked and replaced with '<redacted>'. However, a 'clouds.yaml' file is not the only way to configure these tools. You can also use old school environment variables. By using an openrc file from Horizon (or the clouds2env tool [1]), we will set various 'OS_'-prefixed environment variables. When you use the 'config show' command with these environment variables set, we will see all of these values appear in the output *without* an 'auth.' prefix. Scanning down we will see the password value is not redacted. $ openstack config show +---------------------------------------------+----------------------------------+ | Field | Value | +---------------------------------------------+----------------------------------+ | additional_user_agent | [('osc-lib', '2.6.0')] | | api_timeout | None | ... | password | secret-password | ... This will also happen if using tokens. This is obviously incorrect. These should be masked also. Make it so. This involves enhancing our fake config generation code to generate config that looks like it came from environment variables. Change-Id: I560b928e5e6bcdcd89c409e0678dfc0d0b056c0e Story: 2008816 Task: 42260
1 parent 366e164 commit 62c52f5

3 files changed

Lines changed: 71 additions & 32 deletions

File tree

openstackclient/common/configuration.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def get_parser(self, prog_name):
4545
return parser
4646

4747
def take_action(self, parsed_args):
48-
4948
info = self.app.client_manager.get_configuration()
5049

5150
# Assume a default secret list in case we do not have an auth_plugin
@@ -63,4 +62,9 @@ def take_action(self, parsed_args):
6362
value = REDACTED
6463
info['auth.' + key] = value
6564

65+
if parsed_args.mask:
66+
for secret_opt in secret_opts:
67+
if secret_opt in info:
68+
info[secret_opt] = REDACTED
69+
6670
return zip(*sorted(info.items()))

openstackclient/tests/unit/common/test_configuration.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ class TestConfiguration(utils.TestCommand):
3535
fakes.REGION_NAME,
3636
)
3737

38-
opts = [mock.Mock(secret=True, dest="password"),
39-
mock.Mock(secret=True, dest="token")]
38+
opts = [
39+
mock.Mock(secret=True, dest="password"),
40+
mock.Mock(secret=True, dest="token"),
41+
]
4042

41-
@mock.patch("keystoneauth1.loading.base.get_plugin_options",
42-
return_value=opts)
43+
@mock.patch(
44+
"keystoneauth1.loading.base.get_plugin_options", return_value=opts
45+
)
4346
def test_show(self, m_get_plugin_opts):
4447
arglist = []
4548
verifylist = [('mask', True)]
@@ -51,12 +54,14 @@ def test_show(self, m_get_plugin_opts):
5154
self.assertEqual(self.columns, columns)
5255
self.assertEqual(self.datalist, data)
5356

54-
@mock.patch("keystoneauth1.loading.base.get_plugin_options",
55-
return_value=opts)
57+
@mock.patch(
58+
"keystoneauth1.loading.base.get_plugin_options", return_value=opts
59+
)
5660
def test_show_unmask(self, m_get_plugin_opts):
5761
arglist = ['--unmask']
5862
verifylist = [('mask', False)]
5963
cmd = configuration.ShowConfiguration(self.app, None)
64+
6065
parsed_args = self.check_parser(cmd, arglist, verifylist)
6166

6267
columns, data = cmd.take_action(parsed_args)
@@ -71,15 +76,49 @@ def test_show_unmask(self, m_get_plugin_opts):
7176
)
7277
self.assertEqual(datalist, data)
7378

74-
@mock.patch("keystoneauth1.loading.base.get_plugin_options",
75-
return_value=opts)
76-
def test_show_mask(self, m_get_plugin_opts):
79+
@mock.patch(
80+
"keystoneauth1.loading.base.get_plugin_options", return_value=opts
81+
)
82+
def test_show_mask_with_cloud_config(self, m_get_plugin_opts):
7783
arglist = ['--mask']
7884
verifylist = [('mask', True)]
85+
self.app.client_manager.configuration_type = "cloud_config"
7986
cmd = configuration.ShowConfiguration(self.app, None)
87+
8088
parsed_args = self.check_parser(cmd, arglist, verifylist)
8189

8290
columns, data = cmd.take_action(parsed_args)
8391

8492
self.assertEqual(self.columns, columns)
8593
self.assertEqual(self.datalist, data)
94+
95+
@mock.patch(
96+
"keystoneauth1.loading.base.get_plugin_options", return_value=opts
97+
)
98+
def test_show_mask_with_global_env(self, m_get_plugin_opts):
99+
arglist = ['--mask']
100+
verifylist = [('mask', True)]
101+
self.app.client_manager.configuration_type = "global_env"
102+
column_list = (
103+
'identity_api_version',
104+
'password',
105+
'region',
106+
'token',
107+
'username',
108+
)
109+
datalist = (
110+
fakes.VERSION,
111+
configuration.REDACTED,
112+
fakes.REGION_NAME,
113+
configuration.REDACTED,
114+
fakes.USERNAME,
115+
)
116+
117+
cmd = configuration.ShowConfiguration(self.app, None)
118+
119+
parsed_args = self.check_parser(cmd, arglist, verifylist)
120+
121+
columns, data = cmd.take_action(parsed_args)
122+
123+
self.assertEqual(column_list, columns)
124+
self.assertEqual(datalist, data)

openstackclient/tests/unit/fakes.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14-
#
1514

1615
import json
1716
import sys
@@ -49,21 +48,6 @@
4948
TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL)
5049

5150

52-
def to_unicode_dict(catalog_dict):
53-
"""Converts dict to unicode dict
54-
55-
"""
56-
if isinstance(catalog_dict, dict):
57-
return {to_unicode_dict(key): to_unicode_dict(value)
58-
for key, value in catalog_dict.items()}
59-
elif isinstance(catalog_dict, list):
60-
return [to_unicode_dict(element) for element in catalog_dict]
61-
elif isinstance(catalog_dict, str):
62-
return catalog_dict + u""
63-
else:
64-
return catalog_dict
65-
66-
6751
class FakeStdout(object):
6852

6953
def __init__(self):
@@ -142,18 +126,30 @@ def __init__(self):
142126
self.network_endpoint_enabled = True
143127
self.compute_endpoint_enabled = True
144128
self.volume_endpoint_enabled = True
129+
# The source of configuration. This is either 'cloud_config' (a
130+
# clouds.yaml file) or 'global_env' ('OS_'-prefixed envvars)
131+
self.configuration_type = 'cloud_config'
145132

146133
def get_configuration(self):
147-
return {
148-
'auth': {
149-
'username': USERNAME,
150-
'password': PASSWORD,
151-
'token': AUTH_TOKEN,
152-
},
134+
135+
config = {
153136
'region': REGION_NAME,
154137
'identity_api_version': VERSION,
155138
}
156139

140+
if self.configuration_type == 'cloud_config':
141+
config['auth'] = {
142+
'username': USERNAME,
143+
'password': PASSWORD,
144+
'token': AUTH_TOKEN,
145+
}
146+
elif self.configuration_type == 'global_env':
147+
config['username'] = USERNAME
148+
config['password'] = PASSWORD
149+
config['token'] = AUTH_TOKEN
150+
151+
return config
152+
157153
def is_network_endpoint_enabled(self):
158154
return self.network_endpoint_enabled
159155

0 commit comments

Comments
 (0)