Config: redact sensitive fields with -r option

- `config -r` will show 'REDACTED' instead of the actual value
- Has a default list of field names that should be redacted (e.g., 'password')
- Plugins can add redacted fields that they introduce
This commit is contained in:
Tom Jaspers 2015-03-24 17:00:37 +01:00
parent c4d7dd0d6d
commit 778138f2cd
2 changed files with 29 additions and 2 deletions

View file

@ -1475,7 +1475,7 @@ def config_func(lib, opts, args):
# Dump configuration.
else:
print(config.dump(full=opts.defaults))
print(config.dump(full=opts.defaults, redact_fields=opts.redacted))
def config_edit():
@ -1506,6 +1506,10 @@ config_cmd.parser.add_option(
'-d', '--defaults', action='store_true',
help='include the default configuration'
)
config_cmd.parser.add_option(
'-r', '--redacted', action='store_true',
help='redact sensitive fields'
)
config_cmd.func = config_func
default_commands.append(config_cmd)

View file

@ -387,6 +387,7 @@ class RootView(ConfigView):
"""
self.sources = list(sources)
self.name = ROOT_NAME
self.redacted_fields = set()
def add(self, obj):
self.sources.append(ConfigSource.of(obj))
@ -404,6 +405,12 @@ class RootView(ConfigView):
def root(self):
return self
def add_redacted_fields(self, field_names):
if not isinstance(field_names, list):
field_names = [field_names]
self.redacted_fields = self.redacted_fields | set(field_names)
class Subview(ConfigView):
"""A subview accessed via a subscript of a parent view."""
@ -455,6 +462,9 @@ class Subview(ConfigView):
def root(self):
return self.parent.root()
def add_redacted_fields(self, field_names):
self.parent.add_redacted_fields(field_names)
# Config file paths, including platform-specific paths and in-package
# defaults.
@ -585,11 +595,16 @@ def load_yaml(filename):
# YAML dumping.
REDACTED_FIELDS = ['password', 'username', 'userid', 'apikey',
'apisecret', 'email', ]
class Dumper(yaml.SafeDumper):
"""A PyYAML Dumper that represents OrderedDicts as ordinary mappings
(in order, of course).
"""
redacted_fields = set(REDACTED_FIELDS)
# From http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py
def represent_mapping(self, tag, mapping, flow_style=None):
value = []
@ -600,6 +615,8 @@ class Dumper(yaml.SafeDumper):
if hasattr(mapping, 'items'):
mapping = list(mapping.items())
for item_key, item_value in mapping:
if item_key in Dumper.redacted_fields:
item_value = u'REDACTED'
node_key = self.represent_data(item_key)
node_value = self.represent_data(item_value)
if not (isinstance(node_key, yaml.ScalarNode) and
@ -783,7 +800,7 @@ class Configuration(RootView):
filename = os.path.abspath(filename)
self.set(ConfigSource(load_yaml(filename), filename))
def dump(self, full=True):
def dump(self, full=True, redact_fields=True):
"""Dump the Configuration object to a YAML file.
The order of the keys is determined from the default
@ -803,6 +820,12 @@ class Configuration(RootView):
sources = [s for s in self.sources if not s.default]
out_dict = RootView(sources).flatten()
if redact_fields:
Dumper.redacted_fields = Dumper.redacted_fields | \
self.redacted_fields
else:
Dumper.redacted_fields = set()
yaml_out = yaml.dump(out_dict, Dumper=Dumper,
default_flow_style=None, indent=4,
width=1000)