solid/authorization-panel

Enforce a secure default for client restrictions

Opened this issue · 5 comments

During the authorization panel on 2022-03-02, we discussed open by default client permissions being a security issue.

Currently, in ACP for example, an access mode can be granted without ever requiring a client to be matched. More specifically, the current axiom states:

An access mode MUST be granted over a resource if and only if in the set of policies mandating access over it:

  • A satisfied policy allows it; and
  • No satisfied policy denies it.

ACP Specification - 3.3.2. Granted access modes

That is, an access mode could be granted without even matching an agent (whether via a specific WebID or via the acp:PublicAgent matcher).

One way to restrict client access for an agent could be to enforce it via claims in the access token, for example, via adding a set of trusted web apps to a WebID document. However, declaring trusted web apps in the WebID document comes with some privacy issues as discussed during the authorization panel on 2022-03-02.

Another solution could be, for example, to require at least one acp:agent (could be implicit for the owner agent) and acp:client match for an access mode to be granted. One downside of that approach is that it would increase complexity of ACP implementations and begs questions such as: Would an explicit match in a noneOf matcher count?

The logical follow up question to this might be: Which context attributes must be closed by default? Does this requirement apply at the ACP spec level or at the Solid-ACP level (maybe WAC?) or as a best current practice?

How would we enforce this requirement from a WAC/ACL perspective?

/cc @elf-pavlik @acoburn

One way to restrict client access for an agent could be to enforce it via claims in the access token

I'm not sure if you refer to the access token from the previous version of soild-oidc, in which OP was issuing it. If that's the case OP has no responsibility for managing client authorizations. In Solid Application Interoperability, we put this responsibility on Authorization Agent.

Another solution could be, for example, to require at least one acp:agent (could be implicit for the owner agent) and acp:client match for an access mode to be granted.

@matthieubosquet how ACP currently interprets matcher which does not specify acp:agent? Is it equivalent with acp:agent acp:PublicAgent ?

As we discussed during the call, for me the first change would be to require explicit acp:client acp:PublicClient to allow any client to match.

The logical follow up question to this might be: Which context attributes must be closed by default?

I would also consider defining a shape for acp:Matcher and making all attributes required. At the same time we should provide * wildcards like acp:PublicAgent, acp:PublicClient, acp:PublicIssuer for all of them (or even use acp:Any everywhere).

Access modes as a token claim

I'm not sure if you refer to the access token from the previous version of soild-oidc, in which OP was issuing it.

I am refering to the OP issued token indeed. I understand that it would further complicate the client registration step by adding to it dereferencing the WebID to add the set of modes entrusted to a client app (none by default) as another custom claim in the OP issued token.

In any case, as you rightly mentioned, relying on publicly advertised client "trust" is a privacy concern, which means we would also need a mechanism relying on a less public or more opaque mechanism. But interestingly, the same kind of public disclosure issue affects the issuer claim.

So, maybe this option should not be dismissed off-hand.


ACP matchers

Is it equivalent with acp:agent acp:PublicAgent?

In ACP, matching on something and not matching on something is not the same thing. ACP describes a simple resolution algorithm and the upper "elements" are not aware of what happens below by design. In other words, a Policy doesn't know what the matchers are matching against, just that a positive match was returned. One might very well not explicitly want to match against an agent or a client and it is a perfectly valid use case from ACP's perspective.

Furthermore, given the degree of complexity of ACP, the fact that a match was found for a client or an agent would not be enough. For example, take the following policies both applying to a resource X:

ex:policy1
    acp:allow acl:Read ;
    acp:allOf [
        acp:agent ex:Bob
    ],
    [
        acp:agent ex:Alice
    ] .

ex:policy2
    acp:allow acl:Read ;
    acp:anyOf [
        acp:client ex:AppX
    ] .

Both Bob and Alice got matched, but in a policy that would never apply. They gain access just because they use client app ex:AppX. A more realistic use case would be, imagine you got a positive match related to one access mode but not to another... In other words, to require matches of a specific kind would not be trivial and add a requirement for some form of state management. It would not only complexify the implementations of ACP, but also complexify the writing of permissions in ACP. A likely outcome would be an overuse of an acp:Any kind of matchers.1

I also have a feeling this would not really satisfy the spec orthogonality principle (what if there were a client-less context).

Another concern is that trying to enforce app trust in ACL auxiliary resources only addresses cases where a user has control access over a resource. It seems like a requirement a user would want to enforce globally, not at the resource or pod level.


Leveraging the WebID

Leaving the privacy concern aside for a minute since it is wider ranging than just the question of restricting client access by default, would the Solid OIDC ID token not be an interesting place to enforce the maximum access that could be granted to a client?

For example a WebID could include as per the trusted apps proposal:

# Resource: https://janedoe.com/web
<#id>
    solid:oidcIssuer <https://idp.example.org/>
    acl:trustedApp [
        acl:origin  <https://app.example.org/> ;
        acl:mode  acl:Read, acl:Append
    ] .

And a corresponding ID token could claim:

{
    "webid": "https://janedoe.com/web#id",
    "iss": "https://idp.example.org/",
    "sub": "janedoe",
    "aud": ["https://client.example.com/web#id", "solid"],
    "azp": "https://client.example.com/web#id",
    "modes": ["http://www.w3.org/ns/auth/acl#Read", "http://www.w3.org/ns/auth/acl#Append"],
    "iat": 1311280970,
    "exp": 1311281970,
    "cnf":{
      "jkt":"0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I..."
    }
}

No further access than acl:Read and acl:Append could be gained in that context.

This would have the advantage of:

  1. Giving full control to the user/owner of a WebID of basic client restrictions;
  2. Addressing the issue regardless of which authorization/permissions description ontology (say ACL or ACP or ODRL...) is used to describe resource access in the Solid ACL auxiliary resource;
  3. Therefore respect the spec orthogonality principle.

I also think that the acl:Read mode should be implicitly granted (no need to check the WebID for it) as allowing an app to be used comes with that permission by default.

OIDC scopes as fallback

Finally, couldn't we also leverage classic OIDC scopes to allow the user to choose what they consent to?

By default, in the Solid-OIDC flow, the OP could check the WebID in order to verify whether the client app is publicly advertised as trusted for an access mode (maybe foregoing the consent screen). But in case it is not in the WebID, the user could select which scopes, say "Read the WebID profile doc" and "acl:Read" by default and optional "acl:Write"... they're comfortable with.

This might yet be an ever nicer default resulting in a standard "scope" claim instead of "modes":

{
    "webid": "https://janedoe.com/web#id",
    "iss": "https://idp.example.org/",
    "sub": "janedoe",
    "aud": ["https://client.example.com/web#id", "solid"],
    "azp": "https://client.example.com/web#id",
    "scope": "openid webid http://www.w3.org/ns/auth/acl#Read http://www.w3.org/ns/auth/acl#Append",
    "iat": 1311280970,
    "exp": 1311281970,
    "cnf":{
      "jkt":"0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I..."
    }
}

Footnotes

  1. acp:Any is not part of the ACP spec and used here for ease of conveying the idea as per the previous comment.

I honestly think that the old trusted apps experiment doesn't provide a good starting point.

Solid Application Interoperablity (aka SAI) already provides most of the features allowing End-user to authorize what apps can do on their behalf.

I would like to point out here that we don't only address access modes, we also address what kinds of data the user authorizes the app to access (relying on shape tree definitions), as well how the app can discover all the data it was authorized by the user to access on their behalf.

Trusted app experiment, only provides some degree of control of access modes, lacks privacy, doesn't allow selecting types of data (or specific resources), and doesn't help at all with discovery.

Throughout the rest of this comment, I will respond to things from the perspective of how SAI can solve discussed problems.

Another concern is that trying to enforce app trust in ACL auxiliary resources only addresses cases where a user has control access over a resource. It seems like a requirement a user would want to enforce globally, not at the resource or pod level.

Currently, my preferred approach leaves setting ACLs to the Authorization Server (or another dedicated party solid/data-interoperability-panel#250). It would do it based on a combination Data Grants representing Resource Owner granting End-user specific modes of access to specific kind of data. As well as Delegated Data Grants representing End-user granting Application (client) specific modes of access to specific kinds of data.

This would have the advantage of:

  1. Giving full control to the user/owner of a WebID of basic client restrictions;

That's exactly what SAI is providing. Based on Data Grant End-user received from the Resource Owner, they issue a Delegated Data Grant which delegates (subset) of their access to the application they use. End-user uses their Authorization Agent to issue Delegated Data Grant.

As discussed during recent meetings, those Delegated Data Grants could be made available to the Authorization Server (or another dedicated party solid/data-interoperability-panel#250) by upstream notification mechanism discussed in solid/data-interoperability-panel#222 . In simple, one-step delegation, we can also consider pushing a signed Delegated Data Grant as a claim. It could follow similar verification as the ID Token claim pushed in solid-oidc. With the difference that we wouldn't rely on solid:oidcIssuer in WebID Profile but interop:hasAuthorizationAgent. Either approach doesn't rely on anyone else than a client acting on Resource Owner's behalf to write to ACL auxiliary resources.


I also would like to point out that we are trying hard not to put any AuthZ responsibilities on solid-oidc. Currently it only makes OP responsible for verifying WebID of the End-user and ClientID of the client/application. The OP only issues DPoP-bound OIDC ID Token which later gets pushed as a claim to UMA AS.
IMO an attempt to putting any AuthZ responsibilities involving access modes, kinds of data or even selected resources on OP would be a major departure for current direction.

In SAI dedicated Authorization Agent, which is similar to OP stays associated with the End-user, gets those responsibilities. It also stays responsible for setting access policies which can span across multiple storages (Resource Server) each possibly having its own Authorization Server.

I think I agree with @elf-pavlik on the fact that this goes beyond the scope of Solid-OIDC. The new architecture pattern that we are looking at aims at decoupling the OP from the AS, and I think by essence the ID token should only contain AuthN-related claims, and stay clear of AuthZ ones. Any additions to the OP responsibilities is a higher cost for migrating existing OPs to the Solid ecosystem, while AS/RS don't really make sense outside of the Solid context, so putting some additional burden on them doesn't seem to be as problematic.

It would make sense that the default client restriction for a given user is enforced by the AS when issuing the Access Token. This means that all the data required to enforce it should be discoverable based on the ID token, so a combination of the OP URL, the user's WebID, and the Client Identifier. I don't have enough insight in the interop spec to have an opinion on how appropriate it is to fix this issue.

Could it make sense to this default client restriction to be scoped to a given AS, and therefore only to apply to the resources hosted by RS trusting said AS ? For example, one way of implementing this (not the only one, just a possible one) would be for the AS to add an additional interactive claim gathering step the first time a user uses a given client to access some resources, in a manner that resembles how an OP prompts user for their trust on their first login with a given client. Such interactive claims gathering are documented within UMA for instance, but other authorization schemes could implement different mechanisms resulting in a similar outcome: when accessing data with an unknown Client, the user is asked to define what maximum authorizations the Client should get. The AS could even (as an implementation detail) allow users to go and update this default manually if they want to raise or lower it.

one way of implementing this (not the only one, just a possible one) would be for the AS to add an additional interactive claim gathering step the first time a user uses a given client to access some resources

In SAI the Authorization Agent is responsible for getting authorization for the client from the End-user. It is associated with the End-user and the user has full trust and confidence in it, as well as familiarity with its UI. I would find it much worst UX if the end-user had to interact with 5, 10, 15, 100 different ASs which they neither trust nor feel familiar with whatever UIs they would provide.