Ejemplo n.º 1
0
def verify_resource_policy(policy: str):
    try:
        resource = json.loads(policy)['Statement'][0]['Resource']
        if isinstance(resource, str):
            resource = [resource]
        iam.simulate_custom_policy(
            PolicyInputList=[
                json.dumps({
                    "Version":
                    "2012-10-17",
                    "Statement": [{
                        "Effect":
                        "Allow",
                        "Action": ["fake:Fake"],
                        "Resource":
                        "arn:hca:fus:*:*:resource/fake/1234",
                    }]
                }),
            ],
            ActionNames=["fake:action"],
            ResourceArns=resource,
            ResourcePolicy=policy,
            CallerArn='arn:aws:iam::634134578715:user/anyone')
    except iam.exceptions.InvalidInputException:
        raise FusilladeHTTPException(title="Bad Request",
                                     detail="Invalid resource policy format.")
Ejemplo n.º 2
0
 def update_policy(self, policy_name: str, policy: dict, policy_type: str):
     self.check_actions(policy)
     params = [
         UpdateObjectParams(
             'POLICY',
             'policy_document',
             ValueTypes.BinaryValue,
             self.cd.format_policy(policy),
             UpdateActions.CREATE_OR_UPDATE,
         )
     ]
     try:
         verify_policy(policy, policy_type)
         self.cd.update_object_attribute(
             self.get_policy_reference(policy_name), params,
             self.cd.node_schema)
     except cd_client.exceptions.LimitExceededException as ex:
         raise FusilladeHTTPException(ex)
     except cd_client.exceptions.ResourceNotFoundException:
         raise FusilladeNotFoundException(
             f"{self.get_policy_path(policy_name)} does not exist.")
     else:
         logger.info(
             dict(message="Policy updated",
                  object=dict(type=self.object_type,
                              path_name=self._path_name),
                  policy=dict(link_name=policy_name,
                              policy_type=policy_type)))
Ejemplo n.º 3
0
def _modify_groups(cloud_node, request):
    action = request.args['action']
    resp = {
        'groups': request.json['groups'],
        'action': action,
        f'{cloud_node.object_type}_id': cloud_node.name
    }
    try:
        Group.exists(request.json['groups'])
        if action == 'add':
            cloud_node.add_groups(request.json['groups'])
        elif action == 'remove':
            cloud_node.remove_groups(request.json['groups'])
    except cd_client.exceptions.BatchWriteException as ex:
        resp['msg'] = ex.response['Error']['Message']
        code = 304
    except FusilladeLimitException as ex:
        raise FusilladeHTTPException(detail=ex.reason,
                                     status=requests.codes.conflict,
                                     title="Conflict")
    else:
        resp[
            'msg'] = f"{cloud_node.object_type}'s groups successfully modified."
        code = 200
    return resp, code
Ejemplo n.º 4
0
 def get_policy(self, policy_type: str = 'IAMPolicy'):
     """
     Policy statements follow AWS IAM Policy Grammer. See for grammar details
     https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html
     """
     if policy_type in self.allowed_policy_types:  # check if this policy type is allowed
         if not self.attached_policies.get(policy_type):  # check if we already have the policy
             policy_ref = get_obj_type_path('policy') + self.get_policy_name(policy_type)
             try:
                 resp = self.cd.get_object_attributes(
                     policy_ref,
                     'POLICY',
                     ['policy_document', 'policy_type'],
                     self.cd.node_schema
                 )
                 attrs = dict([(attr['Key']['Name'], attr['Value'].popitem()[1]) for attr in resp['Attributes']])
                 if attrs['policy_type'] != policy_type:
                     logger.warning({'message': "Retrieved policy_type does not match requested policy_type.",
                                     'expected': policy_type,
                                     'received': attrs['policy_type']
                                     })
                 self.attached_policies[policy_type] = json.loads(attrs['policy_document'].decode("utf-8"))
             except cd_client.exceptions.ResourceNotFoundException:
                 pass
         return self.attached_policies.get(policy_type, {})
     else:
         FusilladeHTTPException(
             title='Bad Request',
             detail=f"{self.object_type} cannot have policy type {policy_type}."
             f" Allowed types are: {self.allowed_policy_types}")
Ejemplo n.º 5
0
    def _set_policy(self, statement: Dict[str, Any], policy_type: str = 'IAMPolicy'):
        verify_policy(statement, policy_type)
        params = [
            UpdateObjectParams('POLICY',
                               'policy_document',
                               ValueTypes.BinaryValue,
                               self.cd.format_policy(statement),
                               UpdateActions.CREATE_OR_UPDATE,
                               )
        ]
        try:
            try:
                self.cd.update_object_attribute(get_obj_type_path('policy') + self.get_policy_name(policy_type),
                                                params,
                                                self.cd.node_schema)
            except cd_client.exceptions.ResourceNotFoundException:
                self.create_policy(statement, policy_type, type=self.object_type, name=self.name)
        except cd_client.exceptions.LimitExceededException as ex:
            raise FusilladeHTTPException(ex)
        else:
            logger.info(dict(message="Policy updated",
                             object=dict(
                                 type=self.object_type,
                                 path_name=self._path_name
                             ),
                             policy=dict(
                                 link_name=self.get_policy_name(policy_type),
                                 policy_type=policy_type)
                             ))

        self.attached_policies[policy_type] = None
Ejemplo n.º 6
0
def get_public_key(issuer: str, kid: str) -> bytearray:
    """
    Fetches the public keys from an OIDC Identity provider to verify the JWT. If the key is not found in the public
    key cache, the cache is cleared and a retry is performed.
    :param issuer: the openid provider's domain.
    :param kid: the key identifier for verifying the JWT
    :return: A Public Key
    """
    public_keys = get_public_keys(issuer)
    try:
        return public_keys[kid]
    except KeyError:
        logger.error({
            "message": "Failed to fetched public key from openid provider.",
            "public_keys": public_keys,
            "issuer": issuer,
            "kid": kid
        })
        logger.debug({"message": "Clearing public key cache."})
        get_public_keys.cache_clear()
        public_keys = get_public_keys(issuer)
        try:
            return public_keys[kid]
        except KeyError:
            raise FusilladeHTTPException(
                401, 'Unauthorized',
                f"Unable to verify JWT. KID:{kid} does not exists for issuer:{issuer}."
            )
Ejemplo n.º 7
0
def get_public_keys(issuer: str) -> typing.Dict[str, bytearray]:
    """
    Fetches the public keys from an OIDC Identity provider to verify the JWT and caching for later use.
    :param issuer: the openid provider's domain.
    :param kid: the key identifier for verifying the JWT
    :return: A Public Keys
    """
    resp = session.get(get_jwks_uri(issuer))
    try:
        resp.raise_for_status()
    except requests.exceptions.HTTPError:
        logger.error({
            "message": f"Get {get_jwks_uri(issuer)} Failed",
            "text": resp.text,
            "status_code": resp.status_code,
        })
        raise FusilladeHTTPException(
            503, 'Service Unavailable',
            "Failed to fetched public key from openid provider.")
    else:
        logger.info({
            "message": f"Get {get_jwks_uri(issuer)} Succeeded",
            "response": resp.json(),
            "status_code": resp.status_code
        })

    return {
        key["kid"]: rsa.RSAPublicNumbers(
            e=int.from_bytes(base64.urlsafe_b64decode(key["e"] + "==="),
                             byteorder="big"),
            n=int.from_bytes(
                base64.urlsafe_b64decode(key["n"] + "==="),
                byteorder="big")).public_key(backend=default_backend())
        for key in resp.json()["keys"]
    }
Ejemplo n.º 8
0
    def create(cls,
               name: str,
               statement: Optional[Dict[str, Any]] = None,
               creator=None,
               **kwargs) -> Type['CloudNode']:
        ops = []
        new_node = cls(name)
        _creator = creator if creator else "fusillade"
        ops.append(new_node.cd.batch_create_object(
            get_obj_type_path(cls.object_type),
            new_node.hash_name(name),
            new_node._facet,
            new_node.cd.get_object_attribute_list(facet=new_node._facet, name=name, created_by=_creator, **kwargs)
        ))
        if creator:
            ops.append(User(name=creator).batch_add_ownership(new_node))

        if not statement and not getattr(cls, '_default_policy_path', None):
            pass
        else:
            if not statement:
                statement = get_json_file(cls._default_policy_path)
            ops.extend(new_node.create_policy(statement, run=False, type=new_node.object_type, name=new_node.name))

        try:
            new_node.cd.batch_write(ops)
        except cd_client.exceptions.BatchWriteException as ex:
            if 'LinkNameAlreadyInUseException' in ex.response['Error']['Message']:
                raise FusilladeHTTPException(
                    status=409, title="Conflict", detail=f"The {cls.object_type} named {name} already exists. "
                    f"{cls.object_type} was not modified.")
            else:
                raise FusilladeHTTPException(ex)
        else:
            new_node.cd.get_object_information(new_node.object_ref, ConsistencyLevel=ConsistencyLevel.SERIALIZABLE.name)
            logger.info(dict(message=f"{new_node.object_type} created by {_creator}",
                             object=dict(type=new_node.object_type, path_name=new_node._path_name)))
            logger.info(dict(message="Policy updated",
                             object=dict(
                                 type=new_node.object_type,
                                 path_name=new_node._path_name
                             ),
                             policy=dict(
                                 link_name=new_node.get_policy_name('IAMPolicy'),
                                 policy_type='IAMPolicy')
                             ))
            return new_node
Ejemplo n.º 9
0
def verify_iam_policy(policy: str):
    try:
        iam.simulate_custom_policy(
            PolicyInputList=[policy],
            ActionNames=["fake:action"],
            ResourceArns=["arn:aws:iam::123456789012:user/Bob"])
    except iam.exceptions.InvalidInputException:
        raise FusilladeHTTPException(title="Bad Request",
                                     detail="Invalid iam policy format.")
Ejemplo n.º 10
0
def verify_jwt(token: str) -> typing.Optional[typing.Mapping]:
    """
    Verify the JWT from the request. This is function is referenced in fusillade-api.yml
    securitySchemes.BearerAuth.x-bearerInfoFunc. It's used by connexion to authorize api endpoints that use BearAuth
    securitySchema.

    :param token: the Authorization header in the request.
    :return: Decoded and verified token.
    """
    try:
        unverified_token = jwt.decode(token, verify=False)
        token_header = jwt.get_unverified_header(token)
    except jwt.DecodeError:
        logger.debug({"msg": "Failed to decode token."}, exc_info=True)
        raise FusilladeHTTPException(401, 'Unauthorized',
                                     'Failed to decode token.')

    issuer = unverified_token['iss']
    public_key = get_public_key(issuer, token_header["kid"])
    try:
        verified_tok = jwt.decode(
            token,
            key=public_key,
            issuer=issuer,
            audience=Config.get_audience(),
            algorithms=allowed_algorithms,
        )
        logger.debug({"message": "Token Validated"})
    except jwt.PyJWTError as ex:  # type: ignore
        logger.debug({"message": "Failed to validate token."}, exc_info=True)
        raise FusilladeHTTPException(401, 'Unauthorized',
                                     'Authorization token is invalid') from ex
    tokeninfo_endpoint = [
        i for i in verified_tok['aud']
        if i.endswith('userinfo') or i.endswith('tokeninfo')
    ]
    if tokeninfo_endpoint:
        # Use the OIDC tokeninfo endpoint to get info about the user.
        return get_tokeninfo(tokeninfo_endpoint[0], token)
    else:
        # If No OIDC tokeninfo endpoint is present then this is a google service account and there is no info to
        # retrieve
        return verified_tok
Ejemplo n.º 11
0
 def create(cls,
            resource_type: str,
            name: str,
            owner: str = None,
            **kwargs) -> 'ResourceId':
     ops = []
     new_node = cls(resource_type, name=name)
     _owner = owner if owner else "fusillade"
     ops.append(
         new_node.cd.batch_create_object(
             f'{new_node.resource_type.object_ref}/id', new_node.name,
             new_node._facet,
             new_node.cd.get_object_attribute_list(facet=new_node._facet,
                                                   name=name,
                                                   created_by=_owner,
                                                   **kwargs)))
     if owner:
         ops.append(User(name=owner).batch_add_ownership(new_node))
     try:
         new_node.cd.batch_write(ops)
     except cd_client.exceptions.BatchWriteException as ex:
         if 'LinkNameAlreadyInUseException' in ex.response['Error'][
                 'Message']:
             raise FusilladeHTTPException(
                 status=409,
                 title="Conflict",
                 detail=f"The {cls.object_type} named {name} already exists. "
                 f"{cls.object_type} was not modified.")
         else:
             raise FusilladeHTTPException(ex)
     else:
         new_node.cd.get_object_information(
             new_node.object_ref,
             ConsistencyLevel=ConsistencyLevel.SERIALIZABLE.name)
         logger.info(
             dict(message=f"{new_node.object_type} created",
                  creator=_owner,
                  object=dict(type=new_node.object_type,
                              path_name=new_node._path_name)))
         return new_node
Ejemplo n.º 12
0
def serve_openid_config():
    """
    Part of OIDC
    """
    auth_host = request.headers['host']
    if auth_host != os.environ["API_DOMAIN_NAME"]:
        raise FusilladeHTTPException(
            status=400,
            title="Bad Request",
            detail=
            f"host: {auth_host}, is not supported. host must be {os.environ['API_DOMAIN_NAME']}."
        )
    openid_config = get_openid_config(Config.get_openid_provider()).copy()
    openid_config.update(**proxied_endpoints)
    return ConnexionResponse(body=openid_config, status_code=requests.codes.ok)
Ejemplo n.º 13
0
    def create(cls,
               name: str,
               actions: List[str],
               owner_policy: Dict[str, Any] = None,
               creator: str = None,
               **kwargs) -> 'ResourceType':
        """
        Create a new resource type in cloud directory.

        :param name: The name of the new resource type.
        :param owner_policy: an IAM policy that determines what a resource owner can do to a resource.
        :param creator: The user who initiated the creation of the resource type.
        :param actions: The actions that can be performed on this resource type
        :param kwargs: additional attributes describing the resource type.
        :return:
        """
        ops = []
        new_node = cls(name)
        _creator = creator if creator else "fusillade"
        # Create the node /resource/{resource_type}
        ops.append(
            new_node.cd.batch_create_object(
                get_obj_type_path(cls.object_type),
                name,
                new_node._facet,
                new_node.cd.get_object_attribute_list(
                    facet=new_node._facet,
                    name=name,
                    created_by=_creator,
                    actions=' '.join(actions),
                    # TODO actions should also have descriptions
                    **kwargs),
                batch_reference='type_node'))
        # Create the node /resource/{resource_type}/id
        ops.append(
            new_node.cd.batch_create_object(
                '#type_node', 'id', 'NodeFacet',
                new_node.cd.get_object_attribute_list(facet='NodeFacet',
                                                      name='id',
                                                      created_by=_creator)))
        # Create the node /resource/{resource_type}/policy
        ops.append(
            new_node.cd.batch_create_object(
                '#type_node',
                'policy',
                'NodeFacet',
                new_node.cd.get_object_attribute_list(facet='NodeFacet',
                                                      name='policy',
                                                      created_by=_creator),
                batch_reference='policy_node'))
        # link owner policy to resource type
        if not owner_policy and not getattr(cls, '_default_policy_path', None):
            raise FusilladeHTTPException('Must provide owner policy.')
        else:
            if owner_policy:
                pass
            elif getattr(cls, '_default_policy_path'):
                with open(cls._default_policy_path, 'r') as fp:
                    owner_policy = json.load(fp)
            ops.extend(
                new_node.create_policy('Owner',
                                       owner_policy,
                                       parent_path='#policy_node',
                                       run=False))

        # Execute batch request
        try:
            new_node.cd.batch_write(ops)
        except cd_client.exceptions.BatchWriteException as ex:
            if 'LinkNameAlreadyInUseException' in ex.response['Error'][
                    'Message']:
                raise FusilladeHTTPException(
                    status=409,
                    title="Conflict",
                    detail=f"The {cls.object_type} named {name} already exists. "
                    f"{cls.object_type} was not modified.")
            else:
                raise FusilladeHTTPException(ex)
        else:
            new_node.cd.get_object_information(
                new_node.object_ref,
                ConsistencyLevel=ConsistencyLevel.SERIALIZABLE.name)
            logger.info(
                dict(message=f"{new_node.object_type} created by {_creator}",
                     object=dict(type=new_node.object_type,
                                 path_name=new_node._path_name)))
            logger.info(
                dict(message="Policy updated",
                     object=dict(type=new_node.object_type,
                                 path_name=new_node._path_name),
                     policy=dict(link_name='Owner', policy_type='IAMPolicy')))
            return new_node