diff --git a/CHANGES.md b/CHANGES.md index f3feae0..98fea31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # Changelog +## 0.9.9 +- Add workaround for inconsistent AWS API S3 GetBucketLocation +- validate now exits with non-zero exit code if valiation of any template failed +- the embedded md5 hash in templates are now verified reading the template + ## 0.9.8 - Remove support for FortyTwo legacy mode as AWS now behaves as it should - Add support for embedded custom output yaml format and removed hardcoded kubezero output template diff --git a/cloudbender/__init__.py b/cloudbender/__init__.py index 8ca5d08..93065f4 100644 --- a/cloudbender/__init__.py +++ b/cloudbender/__init__.py @@ -2,7 +2,7 @@ import logging __author__ = "Stefan Reimer" __email__ = "stefan@zero-downtimet.net" -__version__ = "0.9.8" +__version__ = "0.9.9" # Set up logging to ``/dev/null`` like a library is supposed to. diff --git a/cloudbender/cli.py b/cloudbender/cli.py index d1c0c4b..c45d3e0 100644 --- a/cloudbender/cli.py +++ b/cloudbender/cli.py @@ -78,7 +78,9 @@ def validate(cb, stack_names, multi): stacks = _find_stacks(cb, stack_names, multi) for s in stacks: - s.validate() + ret = s.validate() + if ret: + sys.exit(ret) @click.command() diff --git a/cloudbender/exceptions.py b/cloudbender/exceptions.py index fe64b1b..f844a3e 100644 --- a/cloudbender/exceptions.py +++ b/cloudbender/exceptions.py @@ -12,3 +12,7 @@ class InvalidProjectDir(Exception): class InvalidHook(Exception): """My documentation""" + + +class ChecksumError(Exception): + """My documentation""" diff --git a/cloudbender/stack.py b/cloudbender/stack.py index 1d28063..d9e27e1 100644 --- a/cloudbender/stack.py +++ b/cloudbender/stack.py @@ -15,7 +15,7 @@ from .utils import dict_merge, search_refs, ensure_dir, get_s3_url from .connection import BotoConnection from .jinja import JinjaEnv, read_config_file from . import __version__ -from .exceptions import ParameterNotFound, ParameterIllegalValue +from .exceptions import ParameterNotFound, ParameterIllegalValue, ChecksumError from .hooks import exec_hooks import cfnlint.core @@ -207,6 +207,20 @@ class Stack(object): except KeyError: pass + # Get checksum + if not self.md5: + try: + self.md5 = self.cfn_data['Metadata']['Template']['Hash'] + + # Verify embedded md5 hash + source_cfn = re.sub('Hash: [0-9a-f]{32}', 'Hash: __HASH__', self.cfn_template) + our_md5 = hashlib.md5(source_cfn.encode('utf-8')).hexdigest() + if (our_md5 != self.md5): + raise ChecksumError("Template hash checksum mismatch! Expected: {} Got: {}".format(self.md5, our_md5)) from None + + except KeyError: + raise ChecksumError("Template missing Hash checksum!") from None + # Add CloudBender dependencies include = [] search_refs(self.cfn_data, include, self.mode) @@ -315,6 +329,7 @@ class Stack(object): self.cfn_data = yaml.load(self.cfn_template, Loader=SafeLoaderIgnoreUnknown) self._parse_metadata() + else: logger.debug('Using cached cfn template %s.', self.stackname) @@ -356,8 +371,10 @@ class Stack(object): if len(matches): for match in matches: logger.error(formatter._format(match)) + return 1 else: logger.info("Passed.") + return 0 def get_outputs(self, include='.*', values=False): """ gets outputs of the stack """ @@ -766,6 +783,10 @@ class Stack(object): # so we need the region, AWS as usual (bucket, path) = get_s3_url(self.template_bucket_url, self.rel_path, self.stackname + ".yaml") bucket_region = self.connection_manager.call('s3', 'get_bucket_location', {'Bucket': bucket}, profile=self.profile, region=self.region)['LocationConstraint'] + # If bucket is in us-east-1 AWS returns 'none' cause reasons grrr + if not bucket_region: + bucket_region = 'us-east-1' + kwargs['TemplateURL'] = 'https://{}.s3.{}.amazonaws.com/{}'.format(bucket, bucket_region, path) else: kwargs['TemplateBody'] = self.cfn_template