SSO next version
Ideas and fully working prototytpe of an SSO update. The
required spec changes are numbered and indicated in bold.
A PR with these changes has been created:
https://github.com/ivoa-std/SSO/pull/10
Note:
- the
ivoa_bearer
challenge described below has serious unsolved security issues, so will not be used in this form. We currently don't have a way of specifying how to acquire a bearer token. -- MarkTaylor - 2024-05-24
discover authentication
The plan is to use www-authenticate headers so we can use existing challenges and ivoa challenges (that convey more info). The basic form, as defined by
RFC7235, is
www-authenticate: {auth-scheme} {scheme-specific-params}
An implemented example:
curl --head https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/argus/capabilities
HTTP/1.1 200
www-authenticate: ivoa_bearer standard_id="ivo://ivoa.net/sso#tls-with-password", access_url="https://ws-cadc.canfar.net/ac/login"
www-authenticate: ivoa_bearer standard_id="ivo://ivoa.net/sso#OpenID", access_url="https://ws-cadc.canfar.net/ac"
www-authenticate: Bearer
www-authenticate: ivoa_x509 standard_id="ivo://ivoa.net/sso#BasicAA", access_url="https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/cred/auth/priv"
www-authenticate: ivoa_x509
Each www-authenticate challenge tells the client about different methods that can be used to authenticate. I'll try to explain each:
- "www-authenticate: Bearer" is the standard Oauth2 challenge; it means you can authenticate with a (bearer) token (but you have to know out-of-band how to get one).
- "www-authenticate: ivoa_bearer" means the same thing, except that since we will define the ivoa_bearer scheme we can add params to convey extra info: how to get that bearer token. There are two methods for getting a token described above. The first is a normal http form POST (standard_id="ivo://ivoa.net/sso#tls-with-password") and the second is the standard OpenID mechanism; clients can chose whichever one they understand.
- "www-authenticate: ivoa_x509" is an indication that you can authenticate with an X509 client certificate (including self-signed proxy certificates as described in SSO-1.0). There are two variants above: one with optional extra params and one without. The one with params tells the client they can get an acceptable client cert via the described mechanism (for sites that provide their own cert certificate authority) and the second, without params, says that the service accepts client certificates from external CAs. Since CADC provides internally generated client certs and accepts externally signed client certs, we include both headers. NOTE: the use of #BasicAA in the CADC ivoa_x509 challenge: that endpoint already exists and that's how it is currently implemented so this simply describes what CADC does right now. It does not mean everyone has to use BasicAA for that feature and we'll likely augment that service to support #tls-with-password as soon as possible.
- "www-authenticate: ivoa_cookie"? Although CADC/CANFAR does not have a readily usable cookie issuing endpoint, we have also considered/designed an ivoa_cookie challenge that works as above but gives the client a cookie instead of a bearer token or cert. It would work more or less the same way.
So far, the additions/modifications to standards required are as follows:
1. VOSI augmented to require http HEAD support for /capabilities endpoints
2. SSO augmented to define ivoa_bearer, ivoa_cookie, and ivoa_x509 challenges
3. SSO ivo://ivoa.net/sso#tls-with-password extended to specify the form params
In all the above cases, once you have a (bearer) token, you use it with a standard http header: "Authorization: Bearer {token}". If you got a cookie, you would use it with the standard "Cookie: {cookie}".
login: acquiring a token (or cookie or certificate)
For standard_id="ivo://ivoa.net/sso#tls-with-password", the form params are simply "username" and "password" and can be used like this:
curl -v -d 'username=????&password=????' https://ws-cadc.canfar.net/ac/login
< x-vo-authenticated: pdowler
< x-vo-bearer: {token}
{token}
We currently return the token in both the "x-vo-bearer" header and the body to show that either work and allow client writers to see which is preferred. The #tls-with-password standard defines the input (form params) and the challenge (ivoa_bearer) defines the output.
For an ivoa_cookie challenge, the response would be a normal "set-cookie: {cookie}" header.
For an ivoa_x509 challenge, output pretty much needs to be in the body because pem encoded certs span multiple lines - intended to be a file format - and I think we will cause pain if we make up a way to return them in a header. Parsers tend to open and read (an InputStream in java) and that works fine with body; there is code in OpenCADC to do this that has been working for years :-)
Additional standards modifications:
4. SSO challenges describe/specify response form of the login endpoint (access_url), possibly including an "x-vo-bearer" response header.
using: perform authenticated service calls
Using the authentication information is scheme-specific; for bearer tokens it would look like:
curl --head -H 'authorization: bearer {token}' https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/argus/capabilities
HTTP/1.1 200
x-vo-authenticated: pdowler
At a minimum, the same "authorization" header can be used with any other endpoint in the service. Neither "authorization" or "bearer" are case sensitive.
A new response header "x-vo-authenticated", giving the authenticated user identity, should be included in all responses for which authentication has succeeded. This enables clients to tell that they are using a service in an authenticated way, which might not be possible otherwise e.g. for a service with optional authentication.
5. SSO requires/recommends authenticated services to include "x-vo-authenticated" header in all authenticated responses
scope
We discussed how a client would know the scope of a token: which
other services at that site can it also use the token to access?
For CADC/CANFAR, the same token can be used with all services across both the cadc-ccda.hia-iha.nrc-cnrc.gc.ca and canfar.net domains, but we do not really know how to convey that... our multiple domain setup is probably only one complicating use case so it would be good to find out what complications other providers have before thinking too much about this. It is an optimisation to re-use tokens more widely. It may or may not simplify client usage. I think Mark considered that topcat could re-use a token with other services that advertised the same login access_url, but that's not safe because a rogue service could advertise someone else's login access_url and trick the client into re-using tokens. So we do need to figure out something to get past the default of "token for service X only". TBD
Cookie scope would have to be "the domain of the login service" to be consistent with normal http usage.
Client certificate scope would be safe if the client considered all certs as globally usable; the cert would fail in all the ways it can already if that's not true (eg. unknown CA if you try to use a CADC cert at some other site) but it wouldn't be insecure per se since the client only sends the public part when authenticating. It would just be annoying to fail with really poor error messages. It would help clients to know that a cert-issuer is "local" or not (eg the CADC endpoint above is "local" to CADC/CANFAR since the CA cert is internal, so we could in principle add something to tell clients that we don't distribute our CA cert) or "local" could be the default interpretation for that scenario. Maybe not worth worrying about now/ever. TBD.
bootstrap and no-auth vs optional auth vs required auth
Finally, how does a client start and how does a service signal that there is no/optional/required authentication? First, it must be noted that the above www-authenticate challenges can be returned in any response, but typically in 200, 401, or 403. And it must be emphasized that
any endpoint can respond with those challenges if something changes (token expires, attempt to access a protected resource, etc).
For the bootstrap, we have implemented this in all /capabilities endpoints (both HEAD and GET) and clients can use an anon http HEAD request to /capabilities as the lightweight method to see what authentication looks like:
- 200 with no www-authenticate headers: anon
- 200 with www-authenticate headers: authentication optional
- 401 with www-authenticate headers: authentication required
Clients can also check current credentials with http HEAD to /capabilities and then look for "x-vo-authenticated" in the response (see above). Since we are essentially replacing use of securityMethod inside capabilities documents with http headers, I don't see any problem with this final spec change:
6. Modify VOSI to allow /capabilities to respond with 401 (or 403) (also affects TAP 1.1 sec 2; & others?)
Since the purview of VOSI-capabilities is to describe service functionalities, I don't see any need to add another endpoint when this one can serve the purpose and is already "mandatory".
OAuth vs. OpenID vs. OIDC discussion and links
First, some definitions:
- OAuth 2.0 (which is what almost all instances of "OAuth" are) is managed by the IETF (https://datatracker.ietf.org/wg/oauth/, with a more accessible overview at https://oauth.net/specs/), and mostly limits itself to how to exchange credentials the user has for some (more limited) credentials to pass to a service. Client registration is manual/unspecified (for the base standard).
- OpenID 1.x and 2.x are not based on OAuth, and are managed by the OpenID Foundation (rather than the IETF). These are practically obsolete (and are stored under "Obsolete Drafts" on https://openid.net/developers/specs/), but there are providers/clients that still exist. This is very different to both OAuth and OpenID Connect in terms of the implementation.
- OpenID Connect (usually shortened to OIDC) is based on OAuth 2.0 and is managed by the OpenID Foundation (rather than the IETF). This replaces OpenID 2.x (effectively it's a 3.0). The specifications can be found at https://openid.net/wg/connect/specifications/, and add user details (things like email, but also birthdate, address and phone number), as well as discovery and dynamic registration of clients. There are further protocols that sit upon this (for banking/finance and government), but these field-specific protocols do not seem useful for us (and seem to be much more about compliance and privacy (when you collect lots of data about a user) than we need).
OAuth and OIDC both need a "client_id" (a "client_secret" can also be provided, but given the nature of the VO, all clients would count as "public"), and while OIDC allows for dynamic registration, that just means there's a different credential that needs to be used to register clients. Depending on the implementation of the server (and if it is implemented from scratch, vs using one of the various existing authentication systems/codebases such as keycloak or CAS), it may be possible to create a "standard" client_id which all VO clients use to get around this (or we could specify how a separate service to get client_ids would work, and let it handle the "VO-specific" stuff and thereby allow off-the-shelf OAuth/OIDC servers to be used).
Some RFCs/specs that are worth watching/reading/discussing