summaryrefslogtreecommitdiff
path: root/architecture.mdwn
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-09-28 16:42:00 +0300
committerLars Wirzenius <liw@liw.fi>2018-09-28 16:42:00 +0300
commit7d35de384897494190507560a64f83305ca460dd (patch)
tree33f805c8bd55ee611db0191b0f33a6dd44a4db8c /architecture.mdwn
parent179d6bae0747b5b2a9235d8b11aebeae586e24da (diff)
downloadick.liw.fi-7d35de384897494190507560a64f83305ca460dd.tar.gz
Change: update architecture about OIDC use
Diffstat (limited to 'architecture.mdwn')
-rw-r--r--architecture.mdwn268
1 files changed, 261 insertions, 7 deletions
diff --git a/architecture.mdwn b/architecture.mdwn
index f7f216a..ea82edc 100644
--- a/architecture.mdwn
+++ b/architecture.mdwn
@@ -345,18 +345,21 @@ All APIs are provided over TLS only. Access tokens are signed using public
key encryption and the public part of the signing keys is provided
to all API providers at deployment time.
-The access tokens contain the identity of the API client, and a list
-of "scopes", which define what the bearer of the token can do. Each
-API call has its own scope (HTTP method, plus path component of the
-URL).
+The access tokens contain the identity of the API client and possibly
+the end-user, and a list of "scopes", which define what the bearer of
+the token can do. Each API call has its own scope (HTTP method, plus
+path component of the URL).
-Getting an access token
+Getting an access token: icktool and OAuth2
-----------------------------------------------------------------------------
-Ick uses [Qvisqve][] as the IDP solution.
+Ick uses [Qvisqve][] as the IDP solution. For non-interactive API
+clients, which act independently of an end-user, the [OAuth2][]
+protocol is used, and in particular the "client credentials grant"
+variant.
-[Qvisqve]: http://www.qvarn.org/qvisqve/
+[OAuth2]: https://oauth.net/2/
The API client (`icktool`, worker-manager) authenticates itself to the
IDP, and if successful, gets back a signed JSON Web Token. It will
@@ -384,6 +387,257 @@ every API client. There are three exceptions:
willl be fided later.
+Getting an access token: ickui and OpenID Connect
+-----------------------------------------------------------------------------
+
+For use cases where an end-user uses ick interactively, via a web user
+interface, the [OpenID Connect][] (OIDC) is used, in particular the
+"authorization code flow" variant. This is somewhat more complicated
+than the client credentials grant for non-interactive use.
+
+[OpenID Connect]: https://openid.net/specs/openid-connect-core-1_0.html
+
+In summary, there are five entitities involved:
+
+* the end-user who owns (in a legal meaning) the resources involved
+* the "resource server" where the resources technically are: this
+ means the controller and artifact store, and possibly other ick
+ compontents that hold data on behalf of the end-user
+* the IDP (Qvisqve), which authenticates the end-user and gives out
+ access tokens that allow the bearer of the access token to do things
+ with the user's resources
+* the front-end running in the end-user's web browser; this is
+ Javascript and other data loaded into the browser
+* a "facade" that sits between the browser and the resource servers
+
+The facade is necessary for security. We do not trust the browser to
+be keep an access token secure from malware running on the end-user's
+machine or device, including in the browser. The facade runs on what
+is assumed to be a more secure machine, and can thus be trusted with
+the access token. The facade can also provide a more convenient API
+for the front-end than what the actual resource servers provide. The
+facade makes HTTP requests to resource servers on behalf of the
+front-end, and includes the access token to those.
+
+### OIDC protocol overview
+
+* User initiates login, by clicking on a "login" link in the front-end
+ UI. Or else the facade initiates this, when its access token
+ expires. Either way, the browser makes a request the facade's login
+ endpoint.
+* Facade redirects user's browser to Qvisqve.
+ * This is called the "authorization request". It includes some
+ data that's needed to prevent various security intrusions.
+ * Also includes information of what kind of access is wanted
+ ("scopes").
+* Qvisqve lets user authenticate themselves.
+ * Username and password for now, other methods will be added
+ later.
+* Qvisqve redirects user's browser back to the facade.
+ * This includes an "authorization code", which can be used a
+ single time by the facade. The browser will see the
+ authorization code, but since it can be used only once, the
+ consequences of the code leaking are tolerable. (And also, the
+ authorization code is useless on its own.)
+* Facade retrieves an access token from Qvisqve.
+ * Facade authenticates itself to Qvisqve using a pre-registered
+ client id and secret. The request includes the authorization
+ code.
+* Facade uses access token to use resource servers.
+
+
+### The authorization request
+
+The authorization request has the following parameters:
+
+* REQUIRED: `scope`. MUST include `openid`. If it is not there,
+ behaviour is unspecified, Qvisqve should return an error. Any
+ other scope values will be included in the access token, if Qvisqve
+ is configured to allow them for the user and application.
+
+* REQUIRED: `response_type`. Must be `code`.
+
+* REQUIRED: `client_id`. An id Qvisqve knows. If unknown, Qvisqve
+ returns an error. This is the client id for the facade.
+
+* REQUIRED: `redirect_uri`. MUST exactly match one of the
+ callback-URIs pre-registered for the application with Qvisqve.
+
+* RECOMMENDED: `state`. The facade will generate this, and Qvisqve
+ will require this, and will return an error if it is missing. Needed
+ for security (XSRF mitigation).
+
+* Qvisqve will ignore any other parameters, for now. The OIDC protocol
+ defines a bunch, and they may be useful later.
+
+
+### The authorization code flow: Protocol messages
+
+User clicks on a "login" link, or facade gets an error indicating the
+access token it was using has expired. In either case, the facade is
+doing something in response to an HTTP request from the browser.
+
+Facade initiates login with "Authorization request" by returning a 302
+(moved temporarily) response, with a Location header like this:
+
+ HTTP/1.1 302 Found
+ Location: https://qvisqve/auth?
+ response_type=code
+ &scope=openid
+ &client_id=CLIENTID
+ &state=RANDOMSTRING
+ &redirect_uri=CALLBACKURI
+
+Here, `CLIENTID` is the client id the facade has for accessing
+Qvisqve, and `CALLBACKURI` is a URL pre-registered with Qvisqve for
+the facade. `RANDOMSTRING` is a large random value (such as a UUID4),
+which the facade generates and remembers.
+
+The browser follows the redirect, and Qvisqve checks the request
+parameters. If it looks OK, Qvisqve creates an "authorization attempt
+object", and stores `CLIENTID`, `RANDOMSTRING`, and `CALLBACKURI` in
+it. It also generates an object id (a UUID4).
+
+Qvisvqe returns to the browser a login form, asking for username and
+password, plus a hidden form field with the authorization attempt
+object id.
+
+The user fills in the login form, and submits it. The submitted form
+includes the authorization attempt object id field. Qvisqve checks the
+id value corresponds to an existing authorization attempt object, and
+that the credentials are valid. If so, it creates an authorization
+code, stores that into the authorization object, and responds the form
+submission with:
+
+ HTTP/1.1 302 Found
+ Location: CALLBACKURI?code=AUTHZCODE&state=RANDOMSTRING
+
+Here, `CALLBACKURI` and `RANDOMSTRING` are the retrieved from the
+authorization object.
+
+Browser follows the redirect to the facade. The facade checks that
+`RANDOMSTRING` matches one it remembered earlier, and extracts
+`AUTHZCODE` from the request URI.
+
+The facade then makes a request to Qvisqve to get actual tokens:
+
+ POST /token HTTP/1.1
+ Host: qvisqve
+ Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+ Content-Type: application/x-www-form-urlencoded
+
+ grant_type=authorization_code&code=AUTHZCODE&redirect_uri=CALLBACKURI
+
+Here, `AUTHZCODE` comes from the request from the browser, and
+`CALLBACKURI` is the same URI as given in the initial authorization
+request. Note that the `Authorization` header Basic Auth encodes the
+client id and client secret for the facade, as registered with
+Qvisqve. The client id must be the same as given in the initial
+authorization request.
+
+Qvisqve checks the client's credentials (`Authorization` header), and
+the `AUTHZCODE` is one it has generated and that hasn't yet been used,
+and that `CALLBACKURI` is registered with the facade. If all is well,
+it responds the facade's token request:
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json;charset=UTF-8
+ Cache-Control: no-store
+ Pragma: no-cache
+
+ {
+ "access_token":"ACCESSTOKEN",
+ "token_type":"bearer",
+ "expires_in":3600,
+ }
+
+Here, `ACCESSTOKEN` is the access token, a signed JSON Web Token,
+which the facade will use in all future requests to resource servers.
+Scopes in the access token are those listed in the `scope` parameter
+in the initial authorization request, or the subset Qvisqve is
+configured to grant to the user.
+
+When returning the access token, Qvisqve destroys the authorization
+object, so that any further use of the authorization attempt object id
+(via the login form's hidden id field, or the authorization code) will
+fail.
+
+
+### The authorization code flow: Sequence diagram
+
+The "happy path" of the authorization code flow, as a UML sequence
+diagram.
+
+ @startuml
+
+ actor enduser
+ participant browser
+ participant facade
+ participant qvisqve
+ participant resources
+
+ group Successful login
+
+ enduser -> browser : click on "login" in application
+ activate browser
+ browser -> facade : GET https://facade/login
+ activate facade
+ facade -> facade : create, remember STATE value
+ facade -> browser : Redirect to https://qvisqve/auth?manyparams
+ deactivate facade
+
+ browser -> qvisqve : GET https://qvisqve/auth?manyparams
+ activate qvisqve
+ qvisqve -> qvisqve : valid request?\ncreate authz object\nwith STATE, etc
+ qvisqve -> browser : login form with authz object id
+ deactivate qvisqve
+ browser -> enduser : show login form
+ deactivate browser
+
+ enduser -> browser : enter credentials, click on submit
+ activate browser
+ browser -> qvisqve : POST https://qvisqve/auth
+ activate qvisqve
+ qvisqve -> qvisqve : valid request (authz obj id)?\ncredentials OK?\ncreate authz code
+ qvisqve -> browser : redirect to https://facade/callback?code=CODE&state=STATE
+ deactivate qvisqve
+
+ browser -> facade : https://facade/callback?code=CODE&state=STATE
+ activate facade
+ facade -> facade : valid request? (STATE matches)
+ facade -> qvisqve : POST https://qvisqve/token with params, Basic Auth
+ activate qvisqve
+ qvisqve -> qvisqve : client creds OK? params OK?\ncreate access token\ndestroy authz obj
+ qvisqve -> facade : access token
+ deactivate qvisqve
+ facade -> facade : create cookie, associate with access token
+ facade -> browser : logged in page, cookie
+ deactivate facade
+ browser -> enduser : show "you're logged in!"
+ deactivate browser
+
+ group User is already logged in
+
+ enduser -> browser : click on something that requires being logged in
+ activate browser
+ browser -> facade : GET https://facade/something
+ activate facade
+ facade -> facade : got valid cookie?
+ facade -> resources : GET htts://resources/something with access token
+ activate resources
+ resources -> resources : check access token
+ facade <- resources : some resource
+ deactivate resources
+ facade -> browser : some thing
+ deactivate facade
+ broser -> enduser : show thing to user
+ deactivate browser
+
+ end
+
+ @enduml
+
+
The worker-manager
-----------------------------------------------------------------------------