def load_local_groups(self, config): """ Load local groups from config file Save and return SecurityGroups object :param config: :rtype : object """ lg.debug("Loading local configuragion") self.config = config yaml.add_constructor('!include', self._yaml_include) try: with open(config, 'r') as fp: conf = yaml.load(fp) except IOError as e: # Error while loading file raise InvalidConfiguration("Can't read config file %s: %s" % (config, e)) except yaml.YAMLError as e: # Error while parsing YAML if hasattr(e, 'problem_mark'): mark = e.problem_mark raise InvalidConfiguration( "Can't parse config file %s: error at line %s, column %s" % (config, mark.line + 1, mark.column + 1)) else: raise InvalidConfiguration("Can't parse config file %s: %s" % (config, e)) # Remove include keys conf = self._fix_include(conf) lg.debug("Loading local groups") for name, group in conf.iteritems(): # Initialize SGroup object sgroup = SGroup( name, None if not group.has_key('description') else group['description']) if group.has_key('rules'): for rule in group['rules']: if rule.has_key('groups'): # For each group, create separate rule # multiple groups are used only to simplify configuration for group in rule['groups']: rule['groups'] = [group] srule = SRule(owner_id=self.owner_id, **rule) sgroup.add_rule(srule) else: # No groups, initialize SRule object srule = SRule(**rule) # Add it into group sgroup.add_rule(srule) self.groups[name] = sgroup return self.groups
def _check_configuration(self): """ Check configuration """ if self.port and not isinstance(self.port, int): raise InvalidConfiguration('Port must be integer for rule %s not %s' % (self.name, type(self.port).__name__)) if self.port_from and not isinstance(self.port_from, int): raise InvalidConfiguration('Parameter port_from must be integer for rule %s not %s' % (self.name, type(self.port_from).__name__)) if self.port_to and not isinstance(self.port_to, int): raise InvalidConfiguration('Parameter port_to must be integer for rule %s not %s' % (self.name, type(self.port_to).__name__)) if self.protocol and self.protocol not in ['tcp', 'udp', 'icmp', '-1', '6', '17']: raise InvalidConfiguration('Protocol must be tcp (6), udp (17) or icmp (-1), not %s for rule %s' % (self.protocol, self.name))
def load_local_groups(self, config, mode): """ Load local groups from config file Save and return SecurityGroups object :param config: :rtype : object """ lg.debug("Loading local configuragion") self.config = config yaml.add_constructor('!include', self._yaml_include) yaml.add_constructor('!include_dir', self._yaml_include_dir) try: with open(config, 'r') as fp: conf = yaml.load(fp) except IOError as e: # Error while loading file raise InvalidConfiguration("Can't read config file %s: %s" % (config, e)) except yaml.YAMLError as e: # Error while parsing YAML if hasattr(e, 'problem_mark'): mark = e.problem_mark raise InvalidConfiguration( "Can't parse config file %s: error at line %s, column %s" % (config, mark.line + 1, mark.column + 1)) else: raise InvalidConfiguration("Can't parse config file %s: %s" % (config, e)) # Empty config file is considered invalid if not conf: raise InvalidConfiguration("Config file is empty") # Remove include keys conf = self._fix_include(conf) lg.debug("Loading local groups") if isinstance(conf, dict): for name, group in conf.iteritems(): if not self.only_groups or name in self.only_groups: # Initialize SGroup object self.groups[name] = self._load_sgroup(name, group, check_mode=mode) return self.groups
def _load_sgroup(self, name, group, check_mode='strict'): """ Return SGroup object with assigned rules """ # Test len of name and description if len(name) > 255: raise InvalidConfiguration( "Name of group '%s' is longer than 255 chars" % (name)) if group.has_key('description') and len(group['description']) > 255: raise InvalidConfiguration( "Description of group '%s' is longer than 255 chars" % (name)) # Test name and description according to spec if check_mode == 'strict': allowed = '^[a-zA-Z0-9_\- ]+$' if check_mode == 'ascii': allowed = '^[\x20-\x7E]+$' if check_mode == 'vpc': allowed = '^[a-zA-Z0-9 ._\-:/()#,@[\]+=&;{}!$*]+$' if group.has_key('description') and not re.match( allowed, group['description']): raise InvalidConfiguration( "Description of group '%s' is not valid in %s check_mode" % (name, check_mode)) if not re.match(allowed, name): raise InvalidConfiguration( "Name of group '%s' is not valid in %s check_mode" % (name, check_mode)) sgroup = SGroup( name, description=None if 'description' not in group.keys() else group['description'], vpc_id=None if 'vpc_id' not in group.keys() else group['vpc_id']) # Dive into group's rules and create srule objects if group.has_key('rules'): for rule in group['rules']: self._validate(rule, sgroup.name) self._load_rule(sgroup, rule) return sgroup
def _yaml_include(self, loader, node): """ Include another yaml file from main file This is usually done by registering !include tag """ filepath = "%s%s" % ('%s/' % os.path.dirname(self.config) \ if os.path.dirname(self.config) else '', node.value) try: with open(filepath, 'r') as inputfile: return yaml.load(inputfile) except IOError as e: raise InvalidConfiguration("Can't include config file %s: %s" % (filepath, e))
def check_validity(self): """ Checks if groups mentioned in the rules are defined and if ip address is in the correct format (including mask) """ for name, ref in self.groups.iteritems(): for rule in ref.rules: for group in rule.groups: if not self.has_group(group['name']): raise InvalidConfiguration( "Group %s referenced by " "group %s does not exist in the config file" % (group['name'], name)) if rule.cidr: for ip in rule.cidr: if not re.match( '^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.' '([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.' '([01]?\\d\\d?|2[0-4]\\d|25[0-5])\/(\\d|[1-2]\\d|3[0-2])$', ip): raise InvalidConfiguration( "Wrong format of ip address '%s' " "in the config file" % ip)
def _yaml_include(self, loader, node): """ Include another yaml file from main file This is usually done by registering !include tag """ filepath = node.value if not os.path.exists(filepath): filepath = self._add_config_prefix(node.value) try: with open(filepath, 'r') as inputfile: return yaml.load(inputfile) except IOError as e: raise InvalidConfiguration("Can't include config file %s: %s" % (filepath, e))
def _validate(self, rule, group_name): """ Validate given rule """ def _validate_port(item): return ('port' in item or ('port_to' in item and 'port_from' in item)) for keyword in rule: # validate used keywords if keyword not in [ 'cidr', 'groups', 'port', 'protocol', 'port_from', 'port_to', 'to' ]: raise InvalidConfiguration("Unknown keyword: %s in group: %s" % (keyword, group_name)) # either port or port_from & port_to is mandatory # you can specify a rule like this #- cidr: [a.b.c.d / 32] # groups: [sg-group-1] # to: # - {port: 100, protocol: tcp} # - {port: 101, protocol: udp} # - {port: 102, protocol: tcp} # # that means, every rule from 'to' section has to be checked for validity too if 'to' in rule: rules_array_ok = all( _validate_port(rule_array) for rule_array in rule['to']) else: rules_array_ok = False if not (_validate_port(rule) or rules_array_ok): raise InvalidConfiguration( "Either port or (port_from, port_to) must be in rule: %s in group: %s" % (rule, group_name))
def __init__(self, owner_id=None, port=None, port_from=None, port_to=None, groups=None, protocol='tcp', cidr=None, srule_object=None, egress=False): """ Initialize variables """ global ec2 ec2 = sgmanager.ec2 # Set rule id self._ids = self._ids.next() self.object = srule_object self.protocol = protocol self.port = port self.port_from = port_from self.port_to = port_to self.owner_id = owner_id self.egress = egress # All ports allowed but only port_to supplied -> complete port range by setting port_from if not self.port_from and self.port_to: self.port_from = 1 # Single port can't be supplied together with port range if self.port and self.port_from and self.port_to: raise InvalidConfiguration('Single port and port range supplied for rule %s' % self._ids) self.groups = [] self.group = None # Check validity of groups parameter if groups: if not isinstance(groups, list): # Convert to list so it can be full dict structure or simple group name groups = [ groups ] # Unify format for granted group permissions # it has to contain id and group owner (account id) for group in groups: if isinstance(group, dict) and len(group) == 1: # There's only one element in dict (suppose it's name), # convert the dict to unify it try: group = group['name'] except Exception: raise InvalidConfiguration("Group definition doesn't contain name, rule %s" % self._ids) if not isinstance(group, dict): # Empty owner and id, only name supplied, prepare full # structure self.groups.append({ 'name' : group, 'owner': owner_id, 'id' : None, }) else: # ..otherwise suppose we already have required structure self.groups.append(group) # cidr should be None if we have groups if groups: self.cidr = None elif not cidr: # No cidr, no groups, allowed from everywhere self.cidr = ['0.0.0.0/0'] else: # We have cidr and no groups, unify structure # convert string cidr to list if cidr and not isinstance(cidr, list): self.cidr = [ cidr ] else: self.cidr = cidr self.name = self._generate_name() self._check_configuration() lg.debug('Loaded rule %s' % self.name)