cedar-policy / cedar

Implementation of the Cedar Policy Language
https://www.cedarpolicy.com
Apache License 2.0
889 stars 80 forks source link

Return an authentication challenge as described in rfc9470 #1301

Closed KyleChau-GamaTech closed 3 days ago

KyleChau-GamaTech commented 3 weeks ago

Category

Cedar language or syntax features/changes

Describe the feature you'd like to request

As described in rfc9470, in simple API authorization scenarios, an authorization server will determine what authentication technique to use to handle a given request on the basis of aspects such as the scopes requested, the resource, the identity of the client, and other characteristics known at provisioning time. Although that approach is viable in many situations, it falls short in several important circumstances. Consider, for instance, an eCommerce API requiring different authentication strengths depending on whether the item being purchased exceeds a certain threshold, dynamically estimated by the API itself using an opaque logic to the authorization server. An API might also determine that a more recent user authentication is required based on its risk evaluation of the API request.

Policy definition sounds to be a good place to include such a logic. To achieve this purpose, extra syntax is needed to support a comprehensive conditional definition of the return challenge. For instance:


permit(
    principal, 
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"], 
    resource
)
when { 
    context.token.client_id == "52n97d5afhfiu1c4di1k5m8f60" &&
    context.token.scope.contains("MyAPI/purchaseOrder.write") &&
    context.request.amount <= 1000 && 
    context.request.item not in ["knife", "rocket_ship", "pistol"]
}
otherwise {
    when {
        context.request.item in ["knife", "rocket_ship", "pistol"]
    } return {
        response.max_age = 1
        response.acr_value = "law_enforcement_fido"
    }
    when {
        context.request.amount > 1000
    } return {
        response.acr_value = "biometric"
    }
}
;

Describe alternatives you've considered

I considered creating a wrapper to customize the request and response of the policy query interface, such as if {authorizer.query(xxx) == deny} return "biometric". However, this approach would only work if the challenge condition is not complex. Otherwise, we would need to create a mapping in the wrapper to determine which acr_value to return based on the input when the Cedar authorizer returns a deny verdict. This doesn't seem to be an elegant approach, as it requires the wrapper to handle part of the policy decision.

Additional context

No response

Is this something that you'd be interested in working on?

cdisselkoen commented 2 weeks ago

I'm not quite sure I understand the scenario here, but it sounds like you might be able to use Cedar annotations to accomplish this. First I'd split what you have into a single permit policy plus multiple forbid policies:

// this is the same permit as previously
permit(
    principal, 
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"], 
    resource
)
when { 
    context.token.client_id == "52n97d5afhfiu1c4di1k5m8f60" &&
    context.token.scope.contains("MyAPI/purchaseOrder.write") &&
    context.request.amount <= 1000 && 
    context.request.item not in ["knife", "rocket_ship", "pistol"]
};

// then you put the other parts in separate forbid policies

forbid(
    principal,
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"],
    resource
)
when {
    context.request.item in ["knife", "rocket_ship", "pistol"]
};

forbid(
    principal,
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"],
    resource
)
when {
    context.request.amount > 1000
};

And then you can add annotations to the forbid policies, so that when they fire, your application can respond appropriately. Specifically your application can look in the reason field of the DENY response to see the forbid policies which were responsible, and then use the annotations on those policies to decide what further actions to take, for instance, prompting the user to reauthenticate.

@max_age("1")
@acr_value("law_enforcement_fido")
forbid(
    principal,
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"],
    resource
)
when {
    context.request.item in ["knife", "rocket_ship", "pistol"]
};

@acr_value("biometric")
forbid(
    principal,
    action in [MyApplication::Action::"POST", MyApplication::Action::"CheckOut"],
    resource
)
when {
    context.request.amount > 1000
};

I might be misunderstanding your intended logic; perhaps you want the expressions in your return clauses to actually be unless clauses attached to these forbid policies, or something.

adpaco-aws commented 3 days ago

Closing this due to no activity. Please reopen if needed.

KyleChau-GamaTech commented 3 days ago

@adpaco-aws @cdisselkoen Thanks a lot for the suggestion. I think annotation would be sufficient for us to return the deny reason we need.