def validate_sg_rule(plan_data, params, condition): from_port = int(params['from_port']) to_port = int(params['to_port']) assert from_port <= to_port, 'Port range is defined incorrectly within the Scenario. ' \ 'Define it {}-{} instead of {}-{}.'.format(from_port, to_port, to_port, from_port) defined_range = set(range(plan_data['from_port'], plan_data['to_port'] + 1)) defined_network_list = plan_data['cidr_blocks'] given_network = params.get('cidr', None) # Condition: must only have # Fail only if ; # * the ports does not match and defined network is a subset of given network. if condition: given_range = set([int(port) for port in params['ports']]) from_to_port = ','.join(params['ports']) # Set to True if ports are exactly same. port_intersection = given_range == defined_range # Set to True if one of the given networks is a subset. network_check = is_ip_in_cidr(given_network, defined_network_list) if not port_intersection and network_check: raise Failure('{}/{} ports are defined for {} network. ' 'Must be limited to {}/{} and {}'.format( '/'.join(plan_data['protocol']), '{}-{}'.format(plan_data['from_port'], plan_data['to_port']), plan_data['cidr_blocks'], '/'.join(plan_data['protocol']), from_to_port, given_network)) # Condition: must not have # Fail only if ; # * the ports match and networks match else: given_range = set(range(from_port, to_port + 1)) port_intersection = given_range & defined_range from_to_port = str(from_port) + '-' + str(to_port) network_intersection = is_ip_in_cidr(given_network, defined_network_list) if port_intersection and network_intersection: raise Failure('{}/{} ports are defined for {} network.'.format( '/'.join(plan_data['protocol']), '{}-{}'.format(plan_data['from_port'], plan_data['to_port']), plan_data['cidr_blocks'])) return True
def fail(assertion, message): try: assert assertion, 'Failed' except AssertionError as e: raise Failure('for {} on {}. {}.'.format(_step_obj.context.address, _step_obj.context.property_name, message))
def assign_sg_params(rule): from_port = int(rule.get('from_port', 0)) to_port = int(rule.get('to_port', 0)) protocol = [proto for proto in [rule.get('protocol', '-1')]] if protocol[0] == '-1' or type(protocol[0]) is int: protocol = ['tcp', 'udp'] protocol[0] = protocol[0].lower() cidr_blocks = rule.get('cidr_blocks', []) if type(cidr_blocks) is not list: cidr_blocks = [cidr_blocks] if to_port == 0 and from_port == 0: to_port = 65535 if from_port > to_port: raise Failure( 'Invalid configuration from_port can not be bigger than to_port. ' '{} > {} {} in {}'.format(from_port, to_port, protocol, cidr_blocks)) return dict(protocol=protocol, from_port=from_port, to_port=to_port, cidr_blocks=cidr_blocks)
def encryption_is_enabled(_step_obj): for resource in _step_obj.context.stash: if type(resource) is dict: prop = encryption_property.get(resource['type'], None) if not prop: raise TerraformComplianceNotImplemented( 'Encryption property for {} ' 'is not implemented yet.'.format(resource['type'])) encryption_value = seek_key_in_dict( resource.get('values', {}), encryption_property[resource['type']]) if len(encryption_value): encryption_value = encryption_value[0] if type(encryption_value) is dict: encryption_value = encryption_value[encryption_property[ resource['type']]] if not encryption_value: raise Failure( 'Resource {} does not have encryption enabled ({}={}).'. format(resource['address'], prop, encryption_value)) return True
def fail(condition): text = 'matches' if condition == 'must not' else 'does not match' raise Failure('{} property in {} {} {} with {} regex. ' 'It is set to {}.'.format( _step_obj.context.property_name, _step_obj.context.name, _step_obj.context.type, text, regex, values))
def fail(condition, name=None): text = 'matches' if condition == 'must not' else 'does not match' name = name if (name is not None or name is not False) else _step_obj.context.name pattern = 'Null/None' if regex == '\x00' else regex raise Failure('{} property in {} {} {} with {} regex. ' 'It is set to {}.'.format( _step_obj.context.property_name, name, _step_obj.context.type, text, pattern, values))
def it_condition_have_proto_protocol_and_port_port_for_cidr( _step_obj, condition, proto, port, cidr): proto = str(proto) cidr = str(cidr) # Set to True only if the condition is 'only' condition = condition == 'only' # In case we have a range if '-' in port: if condition: raise Failure( '"must only" scenario cases must be used either with individual port ' 'or multiple ports separated with comma.') from_port, to_port = port.split('-') ports = [from_port, to_port] # In case we have comma delimited ports elif ',' in port: ports = [port for port in port.split(',')] from_port = min(ports) to_port = max(ports) else: from_port = to_port = int(port) ports = list(set([str(from_port), str(to_port)])) from_port = int(from_port) if int(from_port) > 0 else 1 to_port = int(to_port) if int(to_port) > 0 else 1 ports[0] = ports[0] if int(ports[0]) > 0 else '1' looking_for = dict(proto=proto, from_port=int(from_port), to_port=int(to_port), ports=ports, cidr=cidr) for security_group in _step_obj.context.stash: if type(security_group['values']) is list: for sg in security_group['values']: check_sg_rules(plan_data=sg, security_group=looking_for, condition=condition) elif type(security_group['values']) is dict: check_sg_rules(plan_data=security_group['values'], security_group=looking_for, condition=condition) else: raise TerraformComplianceInternalFailure( 'Unexpected Security Group, ' 'must be either list or a dict: ' '{}'.format(security_group['values'])) return True
def property_is_enabled(_step_obj, something): for resource in _step_obj.context.stash: if type(resource) is dict: if something in property_match_list: something = property_match_list[something].get(resource['type'], something) property_value = seek_key_in_dict(resource.get('values', {}), something) if len(property_value): property_value = property_value[0] if type(property_value) is dict: property_value = property_value.get(something, Null) if not property_value: raise Failure('Resource {} does not have {} property enabled ({}={}).'.format(resource.get('address', "resource"), something, something, property_value)) return True
def _its_value_condition_contain(_step_obj, condition, value, values): assert condition in ('must', 'must not'), 'Condition should be one of: `must`, `must not`' if isinstance(values, (int, bool, str, float)): values = [values] if isinstance(values, list): values = [str(v) for v in values] _fail_text = 'did not contain' if condition == 'must' else 'contains' fail_message = '{} property in {} {} {}, it is set to {}'.format( _step_obj.context.property_name, _step_obj.context.name, _fail_text, value, values, ) if condition == 'must': assert value in values, fail_message else: assert value not in values, fail_message else: raise Failure('Can only check that if list contains value')
def it_fails(_step_obj): raise Failure('Forcefully failing the scenario on {} {}'.format( _step_obj.context.name, _step_obj.context.type))
def it_condition_contain_something(_step_obj, something): prop_list = [] if _step_obj.context.type in ('resource', 'data'): for resource in _step_obj.context.stash: if type(resource) is not dict: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', resource.get('expressions', {})) found_value = Null found_key = Null if type(values) is dict: found_key = values.get(something, seek_key_in_dict(values, something)) if type(found_key) is not list: found_key = [{something: found_key}] if len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if type(found_key) is dict: found_value = jsonify( found_key.get(something, found_key)) else: found_value = found_key elif type(values) is list: found_value = [] for value in values: if type(value) is dict: # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break if found_key is not Null and len(found_key): found_key = found_key[0] if len( found_key) == 1 else found_key if type(found_key) is dict: found_value.append( jsonify(found_key.get(something, found_key))) if type(found_value) is dict and 'constant_value' in found_value: found_value = found_value['constant_value'] if found_value is not Null and found_value != [] and found_value != '' and found_value != {}: prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) elif 'must' in _step_obj.context_sensitive_sentence: raise Failure('{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return True skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': values = seek_key_in_dict(_step_obj.context.stash, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something return True skip_step( _step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'. format(_step_obj.context.type, something))
def it_fails(_step_obj): raise Failure('Forcefully failing the scenario on {} ({}) {}'.format(_step_obj.context.name, ', '.join(_step_obj.context.addresses), _step_obj.context.type))
def it_condition_contain_something(_step_obj, something): prop_list = [] if _step_obj.context.type == 'resource': for resource in _step_obj.context.stash: if type(resource) is not dict: resource = { 'values': resource, 'address': resource, 'type': _step_obj.context.name } values = resource.get('values', {}) found_value = None found_key = None if type(values) is dict: found_key = seek_key_in_dict(values, something) if len(found_key): found_key = found_key[0] if type(found_key) is dict: found_value = jsonify(found_key[something]) elif type(values) is list: for value in values: if type(value) is dict: # First search in the keys found_key = seek_key_in_dict(value, something) # Then search in the values with 'key' if not found_key: found_key = seek_regex_key_in_dict_values( value, 'key', something) if found_key: found_key = found_key[0] found_value = value.get('value') break else: raise TerraformComplianceInternalFailure( 'Unexpected value type {}. {}'.format( type(value), value)) if found_key: prop_list.append({ 'address': resource['address'], 'values': found_value, 'type': _step_obj.context.name }) elif 'must' in _step_obj.context_sensitive_sentence: raise Failure('{} ({}) does not have {} property.'.format( resource['address'], resource.get('type', ''), something)) if prop_list: _step_obj.context.stash = prop_list _step_obj.context.property_name = something return True skip_step(_step_obj, resource=_step_obj.context.name, message='Can not find any {} property for {} resource in ' 'terraform plan.'.format(something, _step_obj.context.name)) elif _step_obj.context.type == 'provider': values = seek_key_in_dict(_step_obj.context.stash, something) if values: _step_obj.context.stash = values _step_obj.context.property_name = something return True skip_step( _step_obj, resource=_step_obj.context.name, message='Skipping the step since {} type does not have {} property.'. format(_step_obj.context.type, something))