""" Spec validator schema data ISSUES: place regex rule on email addresses, domain name """ # from logging import error import yaml from cerberus import schema_registry from cerberus import Validator # from orgtool.utils import yamlfmt # Schema for validating spec files. Since spec is accumulated from multiple # files, we do not place a 'require' rule on first level keys. Individual spec # files have only a subset of these. # SPEC_FILE_SCHEMA = """ minimum_version: type: string master_account_id: type: string auth_account_id: type: string default_domain: type: string default_email_pattern: type: string required: False nullable: True default_sc_policy: type: string default_ou: type: string move_unmanaged_account: type: boolean default_path: type: string default_smtp_server: type: string org_admin_email: type: string organizational_units: required: False nullable: True type: list schema: type: dict schema: organizational_unit sc_policies: required: False nullable: True type: list schema: type: dict schema: sc_policy accounts: required: False nullable: True type: list unique_in_list: Name schema: type: dict schema: account users: nullable: True required: False type: list schema: type: dict schema: user groups: nullable: True required: False type: list schema: type: dict schema: group delegations: nullable: True required: False type: list schema: type: dict schema: delegation local_users: nullable: True required: False type: list schema: type: dict schema: local_user custom_policies: nullable: True required: False type: list schema: type: dict schema: custom_policy policy_sets: nullable: True required: False type: list schema: type: dict schema: policy_set stacks: nullable: True required: False type: list schema: type: dict schema: stack """ # Schema for validating the fully accumulate spec object. This is where we # ensure all required keys are present. But we do not need to check sub # schema, as that is done during spec_file validation. # SPEC_SCHEMA = """ minimum_version: required: True type: string master_account_id: required: True type: string auth_account_id: required: True type: string default_domain: required: True type: string default_email_pattern: required: True type: string required: False nullable: True default_sc_policy: required: True type: string default_ou: required: True type: string move_unmanaged_account: required: True type: boolean default_path: required: True type: string default_smtp_server: required: True type: string org_admin_email: required: True type: string organizational_units: required: True nullable: True type: list sc_policies: required: True nullable: True type: list accounts: required: True nullable: True type: list unique_in_list: Name users: required: False nullable: True type: list groups: required: False nullable: True type: list delegations: required: False nullable: True type: list local_users: required: False nullable: True type: list custom_policies: required: False nullable: True type: list policy_sets: required: False nullable: True type: list stacks: required: False nullable: True type: list """ ORGANIZATIONAL_UNIT_SCHEMA = """ Name: required: False nullable: True type: string regex: ^[a-zA-Z0-9_.+-]{1,128}$ IncludeConfigPath: required: False nullable: True type: string MountingOUPath: required: False nullable: True type: string PrefixRequired: required: False nullable: True type: string Accounts: required: False nullable: True type: list schema: type: string Child_OU: required: False nullable: True type: list schema: type: dict schema: organizational_unit SC_Policies: required: False nullable: True type: list schema: type: string Tags: required: False nullable: True type: dict allow_unknown: type: string Ensure: required: False type: string allowed: - present - absent """ POLICY_SCHEMA = r""" PolicyName: required: True type: string regex: ^[\w+=,.@-]{1,128}$ Description: required: False type: string Statement: required: True anyof: - type: string - type: list schema: type: dict Ensure: required: False type: string allowed: - present - absent """ ACCOUNT_SCHEMA = r""" Name: required: True type: string regex: ^[\w+=,.@-]{1,50}$ Email: required: False type: string Alias: required: False type: string Tags: required: False nullable: True type: dict allow_unknown: type: string """ USER_SCHEMA = r""" Name: required: True type: string regex: ^[\w+=,.@-]{1,64}$ Email: required: True type: string CN: required: True type: string RequestId: required: False type: string Ensure: required: False type: string allowed: - present - absent """ GROUP_SCHEMA = r""" Name: required: True type: string regex: ^[\w+=,.@-]{1,128}$ Path: required: False type: string nullable: True Members: required: False nullable: True anyof: - type: string allowed: - ALL - type: list schema: type: string ExcludeMembers: required: False nullable: True type: list schema: type: string Policies: required: False nullable: True type: list schema: type: string Ensure: required: False type: string allowed: - present - absent """ LOCAL_USER_SCHEMA = r""" Name: required: True type: string regex: ^[\w+=,.@-]{1,64}$ ContactEmail: required: True type: string RequestId: required: False type: string Description: required: False type: string Service: required: True type: string Account: required: True anyof: - type: string allowed: - ALL - type: list schema: type: string ExcludeAccounts: required: False type: list schema: type: string Policies: required: False type: list schema: type: string Ensure: required: False type: string allowed: - present - absent """ DELEGATION_SCHEMA = r""" RoleName: required: True type: string regex: ^[\w+=,.@-]{1,64}$ Description: required: False type: string TrustingAccount: required: True anyof: - type: string allowed: - ALL - type: list schema: type: string ExcludeAccounts: required: False type: list schema: type: string TrustedGroup: required: False type: string TrustedAccount: required: False type: string RequireMFA: required: False type: boolean Policies: required: True type: list schema: type: string excludes: PolicySet PolicySet: required: True type: string excludes: Policies Path: required: False type: string nullable: True Duration: required: False type: integer min: 3600 max: 43200 Ensure: required: False type: string allowed: - present - absent """ POLICY_SET_SCHEMA = r""" Name: required: True type: string regex: ^[\w+=,.@-]{1,128}$ Description: required: False nullable: True type: string Policies: required: True nullable: True type: list schema: type: string Tags: required: False nullable: True type: list schema: type: dict schema: tag """ TAG_SCHEMA = """ Key: required: True type: string Value: required: False type: string """ STACK_SCHEMA = """ Name: required: True type: string Template: required: True type: string Package: type: boolean Parameters: required: False nullable: True type: list schema: type: dict schema: parameter """ PARAMETER_SCHEMA = """ Key: required: True type: string Value: required: False type: string """ def file_validator(log): schema_registry.add( "organizational_unit", yaml.safe_load(ORGANIZATIONAL_UNIT_SCHEMA), ) schema_registry.add("sc_policy", yaml.safe_load(POLICY_SCHEMA)) schema_registry.add("account", yaml.safe_load(ACCOUNT_SCHEMA)) schema_registry.add("user", yaml.safe_load(USER_SCHEMA)) schema_registry.add("group", yaml.safe_load(GROUP_SCHEMA)) schema_registry.add("local_user", yaml.safe_load(LOCAL_USER_SCHEMA)) schema_registry.add("delegation", yaml.safe_load(DELEGATION_SCHEMA)) schema_registry.add("custom_policy", yaml.safe_load(POLICY_SCHEMA)) schema_registry.add("policy_set", yaml.safe_load(POLICY_SET_SCHEMA)) schema_registry.add("tag", yaml.safe_load(TAG_SCHEMA)) schema_registry.add("stack", yaml.safe_load(STACK_SCHEMA)) schema_registry.add("parameter", yaml.safe_load(PARAMETER_SCHEMA)) log.debug( f"adding subschema to schema_registry: {schema_registry.all().keys()}", ) vfile = OrgToolValidator(yaml.safe_load(SPEC_FILE_SCHEMA)) log.debug(f"file_validator_schema: {vfile.schema}") return vfile # def spec_validator(log): # vspec = OrgToolValidator(yaml.safe_load(SPEC_SCHEMA)) # log.debug("spec_validator_schema: {}".format(vspec.schema)) # return vspec class OrgToolValidator(Validator): def _validate_unique_in_list(self, unique_in_list, field, value): "{'type': 'string'}" """ Enforce uniqueness of fields listed in unique_in_list against a list of objects in value. """ if not value: # existing list of value is empty, then unique list is true, no need to check return # init error object errors = [] # force input to list unique_fields = unique_in_list if type(unique_fields) is not list: unique_fields = [unique_fields] for unique_field in unique_fields: # build hash set hashes = [] for i, channel in enumerate(value): if isinstance(channel[unique_field], dict): h = hash(frozenset(channel[unique_field].items())) else: h = hash(channel[unique_field]) hashes.append(h) # log duplicates for i, h in enumerate(hashes): if hashes.count(h) > 1: if channel[unique_field] not in errors: errors += [channel[unique_field]] # if str(i) not in errors: # errors[str(i)] = {} # errors[str(i)][unique_field] = \ # "value '%s' must be unique in list" % \ # channel[unique_field] # report errors if len(errors) > 0: # self._error(field, errors) self._error( field, "Values for fields {} are not unique. Duplicates found: {}".format( str(unique_fields).strip("[]"), str(errors).strip("[]"), ), )