Skip to content

Commit 8f89698

Browse files
authored
Merge pull request #20 from ramaro/reveal_improve
improved revealing, added support for revealing files in directories
2 parents c982f78 + ca6f04d commit 8f89698

File tree

4 files changed

+147
-23
lines changed

4 files changed

+147
-23
lines changed

kapitan/cli.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
from kapitan.targets import compile_targets
2929
from kapitan.resources import search_imports, resource_callbacks, inventory_reclass
3030
from kapitan.version import PROJECT_NAME, DESCRIPTION, VERSION
31-
from kapitan.secrets import secret_gpg_backend, secret_gpg_write, secret_gpg_reveal
31+
from kapitan.secrets import secret_gpg_backend, secret_gpg_write, secret_gpg_reveal_file
32+
from kapitan.secrets import secret_gpg_reveal_dir, secret_gpg_reveal_raw
3233
from kapitan.errors import KapitanError
3334

3435
logger = logging.getLogger(__name__)
@@ -101,7 +102,7 @@ def main():
101102
action='store_true', default=False)
102103
secrets_parser.add_argument('--reveal', '-r', help='reveal secrets',
103104
action='store_true', default=False)
104-
secrets_parser.add_argument('--file', '-f', help='read file, set "-" for stdin',
105+
secrets_parser.add_argument('--file', '-f', help='read file or directory, set "-" for stdin',
105106
required=True, metavar='FILENAME')
106107
secrets_parser.add_argument('--target-name', '-t', help='grab recipients from target name')
107108
secrets_parser.add_argument('--inventory-path', default='./inventory',
@@ -197,9 +198,12 @@ def main():
197198
secret_gpg_write(gpg_obj, args.secrets_path, args.write, data, args.base64, recipients)
198199
elif args.reveal:
199200
if args.file == '-':
200-
secret_gpg_reveal(gpg_obj, args.secrets_path, None, verify=(not args.no_verify))
201+
secret_gpg_reveal_raw(gpg_obj, args.secrets_path, None, verify=(not args.no_verify))
201202
elif args.file:
202-
# TODO if it is a directory, reveal every file there
203-
with open(args.file) as fp:
204-
secret_gpg_reveal(gpg_obj, args.secrets_path, args.file,
205-
verify=(not args.no_verify))
203+
if os.path.isfile(args.file):
204+
out = secret_gpg_reveal_file(gpg_obj, args.secrets_path, args.file,
205+
verify=(not args.no_verify))
206+
sys.stdout.write(out)
207+
elif os.path.isdir(args.file):
208+
secret_gpg_reveal_dir(gpg_obj, args.secrets_path, args.file,
209+
verify=(not args.no_verify))

kapitan/secrets.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import errno
2121
from functools import partial
2222
import hashlib
23+
import json
2324
import logging
2425
import os
2526
import re
@@ -28,6 +29,8 @@
2829
import gnupg
2930
import yaml
3031

32+
from kapitan.utils import PrettyDumper
33+
3134
logger = logging.getLogger(__name__)
3235

3336
SECRET_TOKEN_TAG_PATTERN = r"(\?{([\w\:\.\-\/]+)})" # e.g. ?{gpg:my/secret/token}
@@ -196,20 +199,23 @@ def reveal_gpg_replace(gpg_obj, secrets_path, match_obj, verify=True, **kwargs):
196199
logger.debug("Revealing %s", token_tag)
197200
return secret_gpg_read(gpg_obj, secrets_path, token, **kwargs)
198201

199-
def secret_gpg_reveal(gpg_obj, secrets_path, filename, verify=True, output=None, **kwargs):
202+
def secret_gpg_reveal_raw(gpg_obj, secrets_path, filename, verify=True, output=None, **kwargs):
200203
"""
201-
read filename and reveal content with secrets to stdout
204+
read filename and reveal content (per line search and replace) with secrets to stdout
202205
set filename=None to read stdin
203206
set verify=False to skip secret hash verification
204207
set output to filename to write to file object, default is stdout
208+
returns string with revealed content when output is not stdout
205209
"""
206210
_reveal_gpg_replace = partial(reveal_gpg_replace, gpg_obj, secrets_path,
207211
verify=verify, **kwargs)
212+
out_raw = ''
208213
if filename is None:
209214
for line in sys.stdin:
210215
revealed = re.sub(SECRET_TOKEN_TAG_PATTERN, _reveal_gpg_replace, line)
211216
if output:
212217
output.write(revealed)
218+
out_raw += revealed
213219
else:
214220
sys.stdout.write(revealed)
215221
else:
@@ -218,5 +224,76 @@ def secret_gpg_reveal(gpg_obj, secrets_path, filename, verify=True, output=None,
218224
revealed = re.sub(SECRET_TOKEN_TAG_PATTERN, _reveal_gpg_replace, line)
219225
if output:
220226
output.write(revealed)
227+
out_raw += revealed
221228
else:
222229
sys.stdout.write(revealed)
230+
231+
return out_raw
232+
233+
def secret_gpg_reveal_obj(gpg_obj, secrets_path, obj, verify=True, **kwargs):
234+
"recursively updates obj with revealed secrets"
235+
def sub_reveal_data(data):
236+
_reveal_gpg_replace = partial(reveal_gpg_replace, gpg_obj, secrets_path,
237+
verify=verify, **kwargs)
238+
return re.sub(SECRET_TOKEN_TAG_PATTERN, _reveal_gpg_replace, data)
239+
240+
if isinstance(obj, dict):
241+
for k, v in obj.iteritems():
242+
obj[k] = secret_gpg_reveal_obj(gpg_obj, secrets_path, v, verify, **kwargs)
243+
elif isinstance(obj, list):
244+
obj = [secret_gpg_reveal_obj(gpg_obj, secrets_path, item, verify, **kwargs) for item in obj]
245+
elif isinstance(obj, basestring): # XXX this is python 2 specific
246+
obj = sub_reveal_data(obj)
247+
248+
return obj
249+
250+
def secret_gpg_reveal_dir(gpg_obj, secrets_path, dirname, verify=True, **kwargs):
251+
"prints grouped output for revealed file types"
252+
out_json = ''
253+
out_yaml = ''
254+
out_raw = ''
255+
# find yaml/json/raw files and group their outputs
256+
for f in os.listdir(dirname):
257+
full_path = os.path.join(dirname, f)
258+
if not os.path.isfile(full_path):
259+
pass
260+
if f.endswith('.json'):
261+
out_json += secret_gpg_reveal_file(gpg_obj, secrets_path, full_path,
262+
verify=verify, **kwargs)
263+
elif f.endswith('.yml'):
264+
out_yaml += secret_gpg_reveal_file(gpg_obj, secrets_path, full_path,
265+
verify=verify, **kwargs)
266+
else:
267+
out_raw += secret_gpg_reveal_file(gpg_obj, secrets_path, full_path,
268+
verify=verify, **kwargs)
269+
if out_json:
270+
sys.stdout.write(out_json)
271+
if out_yaml:
272+
sys.stdout.write(out_yaml)
273+
if out_raw:
274+
sys.stdout.write(out_raw)
275+
276+
def secret_gpg_reveal_file(gpg_obj, secrets_path, filename, verify=True, **kwargs):
277+
"detects type and reveals file, returns revealed output string"
278+
out = None
279+
if filename.endswith('.json'):
280+
logger.debug("secret_gpg_reveal_file: revealing json file: %s", filename)
281+
with open(filename) as fp:
282+
obj = json.load(fp)
283+
rev_obj = secret_gpg_reveal_obj(gpg_obj, secrets_path, obj,
284+
verify=verify, **kwargs)
285+
out = json.dumps(rev_obj, indent=4, sort_keys=True)
286+
elif filename.endswith('.yml'):
287+
logger.debug("secret_gpg_reveal_file: revealing yml file: %s", filename)
288+
with open(filename) as fp:
289+
obj = yaml.safe_load(fp)
290+
rev_obj = secret_gpg_reveal_obj(gpg_obj, secrets_path, obj,
291+
verify=verify, **kwargs)
292+
out = yaml.dump(rev_obj, Dumper=PrettyDumper,
293+
default_flow_style=False, explicit_start=True)
294+
else:
295+
logger.debug("secret_gpg_reveal_file: revealing raw file: %s", filename)
296+
devnull = open(os.devnull, 'w')
297+
out = secret_gpg_reveal_raw(gpg_obj, secrets_path, filename, output=devnull,
298+
verify=verify, **kwargs)
299+
return out

kapitan/targets.py

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ def compile_jsonnet(file_path, compile_path, search_path, ext_vars, **kwargs):
183183
file_path = os.path.join(compile_path, '%s.%s' % (item_key, output))
184184
with CompiledFile(file_path, mode="w", secrets_path=secrets_path,
185185
secrets_reveal=secrets_reveal, gpg_obj=gpg_obj) as fp:
186-
json.dump(item_value, fp, indent=4, sort_keys=True)
186+
fp.write_json(item_value)
187187
logger.debug("Wrote %s", file_path)
188188
elif output == 'yaml':
189189
file_path = os.path.join(compile_path, '%s.%s' % (item_key, "yml"))
190190
with CompiledFile(file_path, mode="w", secrets_path=secrets_path,
191191
secrets_reveal=secrets_reveal, gpg_obj=gpg_obj) as fp:
192-
yaml.dump(item_value, stream=fp, Dumper=PrettyDumper, default_flow_style=False)
192+
fp.write_yaml(item_value)
193193
logger.debug("Wrote %s", file_path)
194194
else:
195195
raise ValueError('output is neither "json" or "yaml"')
@@ -246,6 +246,56 @@ def __init__(self, context, fp, **kwargs):
246246
self.fp = fp
247247
self.kwargs = kwargs
248248

249+
def write(self, data):
250+
"write data into file"
251+
secrets_reveal = self.kwargs.get('secrets_reveal', False)
252+
if secrets_reveal:
253+
self.fp.write(self.sub_token_reveal_data(data))
254+
else:
255+
self.fp.write(self.sub_token_compiled_data(data))
256+
257+
def write_yaml(self, obj):
258+
"recursively hash or reveal secrets and convert obj to yaml and write to file"
259+
secrets_reveal = self.kwargs.get('secrets_reveal', False)
260+
if secrets_reveal:
261+
self.sub_token_reveal_obj(obj)
262+
else:
263+
self.sub_token_compiled_obj(obj)
264+
yaml.dump(obj, stream=self.fp, Dumper=PrettyDumper, default_flow_style=False)
265+
266+
def write_json(self, obj):
267+
"recursively hash or reveal secrets and convert obj to json and write to file"
268+
secrets_reveal = self.kwargs.get('secrets_reveal', False)
269+
if secrets_reveal:
270+
self.sub_token_reveal_obj(obj)
271+
else:
272+
self.sub_token_compiled_obj(obj)
273+
json.dump(obj, self.fp, indent=4, sort_keys=True)
274+
275+
def sub_token_compiled_obj(self, obj):
276+
"recursively find and replace tokens with hashed tokens in obj"
277+
if isinstance(obj, dict):
278+
for k, v in obj.iteritems():
279+
obj[k] = self.sub_token_compiled_obj(v)
280+
elif isinstance(obj, list):
281+
obj = map(self.sub_token_compiled_obj, obj)
282+
elif isinstance(obj, basestring): # XXX this is python 2 specific
283+
obj = self.sub_token_compiled_data(obj)
284+
285+
return obj
286+
287+
def sub_token_reveal_obj(self, obj):
288+
"recursively find and reveal token tags in data"
289+
if isinstance(obj, dict):
290+
for k, v in obj.iteritems():
291+
obj[k] = self.sub_token_reveal_obj(v)
292+
elif isinstance(obj, list):
293+
obj = map(self.sub_token_reveal_obj, obj)
294+
elif isinstance(obj, basestring): # XXX this is python 2 specific
295+
obj = self.sub_token_reveal_data(obj)
296+
297+
return obj
298+
249299
def hash_token_tag(self, token_tag):
250300
"""
251301
suffixes a secret's hash to its tag:
@@ -276,29 +326,22 @@ def reveal_token_tag(self, token_tag):
276326
return secret_gpg_read(gpg_obj, secrets_path, token)
277327

278328

279-
def sub_token_compiled(self, data):
329+
def sub_token_compiled_data(self, data):
280330
"find and replace tokens with hashed tokens in data"
281331
def _hash_token_tag(match_obj):
282332
token_tag, _ = match_obj.groups()
283333
return self.hash_token_tag(token_tag)
284334

285335
return re.sub(SECRET_TOKEN_TAG_PATTERN, _hash_token_tag, data)
286336

287-
def sub_token_reveal(self, data):
337+
def sub_token_reveal_data(self, data):
288338
"find and reveal token tags in data"
289339
def _reveal_token_tag(match_obj):
290340
token_tag, _ = match_obj.groups()
291341
return self.reveal_token_tag(token_tag)
292342

293343
return re.sub(SECRET_TOKEN_TAG_PATTERN, _reveal_token_tag, data)
294344

295-
def write(self, data):
296-
secrets_reveal = self.kwargs.get('secrets_reveal', False)
297-
if secrets_reveal:
298-
self.fp.write(self.sub_token_reveal(data))
299-
else:
300-
self.fp.write(self.sub_token_compiled(data))
301-
302345
class CompiledFile(object):
303346
def __init__(self, name, **kwargs):
304347
self.name = name

tests/test_secrets.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import tempfile
2222
import gnupg
2323
from kapitan.secrets import secret_token_attributes, SECRET_TOKEN_TAG_PATTERN
24-
from kapitan.secrets import secret_gpg_write, secret_gpg_reveal
24+
from kapitan.secrets import secret_gpg_write, secret_gpg_reveal_raw
2525

2626
GPG_HOME = tempfile.mkdtemp()
2727
GPG_OBJ = gnupg.GPG(gnupghome=GPG_HOME)
@@ -52,7 +52,7 @@ def test_gpg_secret_write_reveal(self):
5252
with open(file_with_secret_tags, 'w') as fp:
5353
fp.write('I am a file with a ?{gpg:secret/sauce:deadbeef}')
5454
with open(file_revealed, 'w') as fp:
55-
secret_gpg_reveal(GPG_OBJ, SECRETS_HOME, file_with_secret_tags,
55+
secret_gpg_reveal_raw(GPG_OBJ, SECRETS_HOME, file_with_secret_tags,
5656
verify=False, output=fp, passphrase="testphrase")
5757
with open(file_revealed) as fp:
5858
self.assertEqual("I am a file with a super secret value", fp.read())
@@ -72,7 +72,7 @@ def test_gpg_secret_base64_write_reveal(self):
7272
with open(file_with_secret_tags, 'w') as fp:
7373
fp.write('I am a file with a ?{gpg:secret/sauce:deadbeef}')
7474
with open(file_revealed, 'w') as fp:
75-
secret_gpg_reveal(GPG_OBJ, SECRETS_HOME, file_with_secret_tags,
75+
secret_gpg_reveal_raw(GPG_OBJ, SECRETS_HOME, file_with_secret_tags,
7676
verify=False, output=fp, passphrase="testphrase")
7777
with open(file_revealed) as fp:
7878
self.assertEqual("I am a file with a c3VwZXIgc2VjcmV0IHZhbHVl", fp.read())

0 commit comments

Comments
 (0)