Skip to content

Commit 3eaa70e

Browse files
committed
Don't barf on Yaml with tags
Yaml can contain custom tags in the form of '!vault' and such. Before this commit, this would cause Yaml to be unable to read the entire yaml file. This commit introduces a custom yaml loader that implements the SafeLoader class and overrides what happens when tags are encountered. For `vault` tags, it returns an 'ENCRYPTED CONTENTS REDACTED' string. For all other (unknown) tags, it returns the tag value as a string. This should also fix "Skipping encrypted file'-like warnings when generating CMDBs.
1 parent 1a00de7 commit 3eaa70e

File tree

6 files changed

+77
-37
lines changed

6 files changed

+77
-37
lines changed

example/host_vars/eek.electricmonk.nl

+8
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ fact_with_int_keys: {
44
1: "hoi",
55
2: 1
66
}
7+
password: !vault |
8+
$ANSIBLE_VAULT;1.1;AES256
9+
35366338653734663863626465336664363333383433326233343339356231363064346366656232
10+
6163653539393537653532306464313738633330363561390a363530636531346338343338383962
11+
62626663376161393639393962653862363931356431376635613034616534363266633665363436
12+
6364616662346362370a373337316636396438623438383965636164633138386435626333643566
13+
6439
14+

src/ansiblecmdb/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
from . import ihateyaml
12
from .parser import HostsParser, DynInvParser
23
from .ansible import Ansible

src/ansiblecmdb/ansible.py

+19-23
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44
import subprocess
55
import codecs
66
import logging
7-
try:
8-
import yaml
9-
except ImportError:
10-
import yaml3 as yaml
11-
12-
7+
from . import ihateyaml
138
import ansiblecmdb.util as util
149
import ansiblecmdb.parser as parser
1510

@@ -182,18 +177,25 @@ def _parse_hostvar_file(self, hostname, path):
182177
try:
183178
self.log.debug("Reading host vars from {}".format(path))
184179
f = codecs.open(path, 'r', encoding='utf8')
185-
invars = yaml.safe_load(f)
180+
invars = ihateyaml.safe_load(f)
186181
f.close()
187-
188-
if invars is not None:
189-
if hostname == "all":
190-
# Hostname 'all' is special and applies to all hosts
191-
for hostname in self.hosts_all():
192-
self.update_host(hostname, {'hostvars': invars}, overwrite=False)
193-
else:
194-
self.update_host(hostname, {'hostvars': invars}, overwrite=True)
195182
except Exception as err:
183+
# Just catch everything because yaml...
196184
self.log.warning("Yaml couldn't load '{0}'. Skipping. Error was: {1}".format(path, err))
185+
return
186+
187+
if invars is None:
188+
# Empty file or whatever. This is *probably* a side-effect of our
189+
# own yaml.SafeLoader implementation ('ihateyaml'), because this
190+
# problem didn't exist before.
191+
return
192+
193+
if hostname == "all":
194+
# Hostname 'all' is special and applies to all hosts
195+
for hostname in self.hosts_all():
196+
self.update_host(hostname, {'hostvars': invars}, overwrite=False)
197+
else:
198+
self.update_host(hostname, {'hostvars': invars}, overwrite=True)
197199

198200
def _parse_groupvar_dir(self, inventory_path):
199201
"""
@@ -218,19 +220,13 @@ def _parse_groupvar_dir(self, inventory_path):
218220
# filename is the group name
219221
groupname = strip_exts(filename, ('.yml', '.yaml', '.json'))
220222

221-
# Check for ansible-vault files, because they're valid yaml for
222-
# some reason... (psst, the reason is that yaml sucks)
223-
first_line = open(full_path, 'r').readline()
224-
if first_line.startswith('$ANSIBLE_VAULT'):
225-
self.log.warning("Skipping encrypted vault file {0}".format(full_path))
226-
continue
227-
228223
try:
229224
self.log.debug("Reading group vars from {}".format(full_path))
230225
f = codecs.open(full_path, 'r', encoding='utf8')
231-
invars = yaml.safe_load(f)
226+
invars = ihateyaml.safe_load(f)
232227
f.close()
233228
except Exception as err:
229+
# Just catch everything because yaml...
234230
self.log.warning("Yaml couldn't load '{0}' because '{1}'. Skipping".format(full_path, err))
235231
continue # Go to next file
236232

src/ansiblecmdb/ihateyaml.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""
2+
Custom Yaml library wrapper, because Yaml is a bag of shit.
3+
"""
4+
5+
try:
6+
import yaml
7+
except ImportError:
8+
import yaml3 as yaml
9+
10+
class StupidYAMLShit(yaml.SafeLoader):
11+
"""
12+
FUCK PYYAML. This class overrides some insanely deep shit which took at
13+
least two hours to get working. This class overrides SafeLoader and handles
14+
tags (e.g. '!bullshit') nonsense in PyYAML, because obviously there is no
15+
ignore_tags option or a simple callback that actually works. That would be
16+
user-friendly, and user-friendliness is insanity, amirite?!
17+
18+
Also, there is no single entry point to hook into this, so we need to
19+
specifically inherit from *SafeLoader* and not from *Loader*. Thanks for
20+
wasting some more of my fucking time PyYAML, you turd.
21+
22+
On a pyyaml-rage-induced side note: How many apps are vulnerable because
23+
all the PyYaml docs mention 'load' and not 'safe_load'? Was this diseased
24+
pile of gunk written by a PHP programmer?!
25+
26+
"""
27+
def handle_tag(self, node_name, node):
28+
# I just *know* there are gonna be problems with simply returning a
29+
# Scalar, but I don't give a fuck at this point.
30+
if node_name == "vault":
31+
new_node = yaml.ScalarNode(node_name, 'ENCRYPTED CONTENTS REDACTED')
32+
else:
33+
new_node = yaml.ScalarNode(node_name, node.value)
34+
35+
return self.construct_scalar(new_node)
36+
37+
38+
# Fugly!
39+
StupidYAMLShit.add_multi_constructor(
40+
u'!',
41+
StupidYAMLShit.handle_tag)
42+
43+
44+
def safe_load(contents):
45+
return yaml.load(contents, Loader=StupidYAMLShit)

src/ansiblecmdb/parser.py

+2-12
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,7 @@
88
import ushlex as shlex
99
else:
1010
import shlex
11-
try:
12-
import yaml
13-
except ImportError as err:
14-
import yaml3 as yaml
15-
16-
17-
def default_ctor(loader, tag_suffix, node):
18-
return tag_suffix + ' ' + node.value
19-
20-
21-
yaml.add_multi_constructor('', default_ctor)
11+
from . import ihateyaml
2212

2313

2414
class HostsParser(object):
@@ -189,7 +179,7 @@ def _parse_line_vars(self, line):
189179
k, v = line.strip().split('=', 1)
190180
if v.startswith('['):
191181
try:
192-
list_res = yaml.safe_load(v)
182+
list_res = ihateyaml.safe_load(v)
193183
if isinstance(list_res[0], dict):
194184
key_values = list_res[0]
195185
return key_values

test/test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import imp
44
import os
55

6-
sys.path.insert(0, '../lib')
7-
sys.path.insert(0, '../src')
6+
sys.path.insert(0, os.path.realpath('../lib'))
7+
sys.path.insert(0, os.path.realpath('../src'))
88
import ansiblecmdb
99

1010

0 commit comments

Comments
 (0)