From af546e35b27603ad0e7525f5b10ece3dc421d6d2 Mon Sep 17 00:00:00 2001 From: Thomas Jaeckle Date: Thu, 21 Jan 2021 10:32:29 +0100 Subject: [PATCH] [#926] added Blogpost about the new policy actions feature * added "Authenticated subjects" section to basic-auth * adjusted the "Subjects" section in basic-policy to be more detailled * fixed links Signed-off-by: Thomas Jaeckle --- ...licy-subject-activate-token-integration.md | 190 ++++++++++++++++++ .../main/resources/pages/ditto/basic-auth.md | 12 ++ .../resources/pages/ditto/basic-policy.md | 149 ++++++++------ .../pages/ditto/installation-operating.md | 4 +- .../pages/ditto/release_notes_120.md | 2 +- 5 files changed, 291 insertions(+), 66 deletions(-) create mode 100644 documentation/src/main/resources/_posts/2021-01-22-policy-subject-activate-token-integration.md diff --git a/documentation/src/main/resources/_posts/2021-01-22-policy-subject-activate-token-integration.md b/documentation/src/main/resources/_posts/2021-01-22-policy-subject-activate-token-integration.md new file mode 100644 index 0000000000..46ea378b53 --- /dev/null +++ b/documentation/src/main/resources/_posts/2021-01-22-policy-subject-activate-token-integration.md @@ -0,0 +1,190 @@ +--- +title: "Policy actions: token based subject activation" +published: true +permalink: 2021-01-22-policy-subject-activate-token-integration.html +layout: post +author: thomas_jaeckle +tags: [blog] +hide_sidebar: true +sidebar: false +toc: true +--- + +The upcoming version of Eclipse Ditto **2.0.0** will be enhanced with the ability to +[alter policies based on policy actions](basic-policy.html#actions). + +## Policy actions + +This new concept of [Policy actions](basic-policy.html#actions) allows upfront defined modifications to policies without +the need for the one invoking the action to have "WRITE" permissions granted on the policy. + +## Activate token based activation of subject + +Together with the concept of actions, a first action named +[`activateTokenIntegration`](basic-policy.html#action-activatetokenintegration) is added. +This action +* only works when using JWT + based authentication issued by Google or other OpenID Connect providers as + [documented in the installation/operation guide](installation-operating.html#openid-connect) +* checks whether the [authenticated subjects](basic-auth.html#authenticated-subjects) which invoked the action have the + permission to `EXECUTE` the action on a policy entry +* checks whether the [authenticated subjects](basic-auth.html#authenticated-subjects) which invoked the action have at + least some kind of `READ` permission to any `thing:/` resource in a policy entry + +When all the conditions were met for a policy entry, the action will inject a new [subject](basic-policy.html#subjects) +into the matched policy entry which by default (the +[pattern is configurable](basic-policy.html#action-activatetokenintegration)) is the following. +This syntax uses [placeholders](basic-placeholders.html) in order to extract information from the authenticated JWT and +the policy entry: +``` +{%raw%} +integration:{{policy-entry:label}}:{{jwt:aud}} +{%endraw%} +``` + +The value of the injected subject will contain the [expiry](basic-policy.html#expiring-policy-subjects) timestamp +copied from the JWT `"exp"` (the expiration time of the token) claim. + +## Example use case + +Assuming that you have configured a custom OpenID Connect provider `some-openid-connect-provider` as +[documented in the installation/operation guide](installation-operating.html#openid-connect): +``` +ditto.gateway.authentication { + oauth { + openid-connect-issuers = { + some-openid-connect-provider = "https://some-openid-connect-provider.com" + } + } +} +``` + +Let's describe our scenario: +* It is required to enable that a Ditto [connection](basic-connections.html) (e.g. an +[HTTP connection](connectivity-protocol-bindings-http.html) invoking an HTTP webhook) shall receive events whenever +the temperature of a twin is modified +* For security reasons however, the webhook shall not receive events longer than the expiration time of the JWT which +was used in order to activate the webhook +* The webhook can be extended by invoking the action again before the "expiry" time was reached + +The underlying [policy](basic-policy.html) shall be the following one: +```json +{ + "policyId": "my.namespace:policy-a", + "entries": { + "owner": { + "subjects": { + "some-openid-connect-provider:some-admin-id": { + "type": "authenticated via OpenID connect provider " + } + }, + "resources": { + "thing:/": { + "grant": ["READ", "WRITE"], + "revoke": [] + }, + "policy:/": { + "grant": ["READ", "WRITE"], + "revoke": [] + } + } + }, + "temperature-observer": { + "subjects": { + "some-openid-connect-provider:some-user-id": { + "type": "authenticated via OpenID connect provider " + } + }, + "resources": { + "thing:/features/temperature": { + "grant": ["READ"], + "revoke": [] + }, + "policy:/entries/temperature-observer/actions/activateTokenIntegration": { + "grant": ["EXECUTE"], + "revoke": [] + } + } + } + } +} +``` + +The policy entry `"temperature-observer"` above describes that: +* the user "some-user-id" may `READ` the `"temperature"` feature of things using this policy +* is allowed to `EXECUTE` the `activateTokenIntegration` action in order to inject a subject derived from his provided + JWT + +Let's assume that the authenticated JWT used for executing the action contained the following claims: +```json +{ + "iss": "https://some-openid-connect-provider.com", + "sub": "some-user-id", + "exp": 1622802633, + "aud": "some-specific-audience-0815" +} +``` + +The "exp" field contains the token expiry timestamp (seconds since epoch) and resolves to: +`Friday, June 4, 2021 10:30:33 AM`. + +Once the HTTP API +[POST /api/2/policies/{policyId}/entries/{label}/actions/activateTokenIntegration](/http-api-doc.html#/Policies/post_policies__policyId__entries__label__actions_activateTokenIntegration), with `policyId=my.namespace:policy-a` and `label=temperature-observer`, +is invoked (without any payload), a new subject will be injected when the +[described prerequisites](basic-policy.html#action-activatetokenintegration) were enforced successfully. + +As a simplification, all possible policy entries may be injected with the subject by invoking the top level action +[POST /api/2/policies/{policyId}/actions/activateTokenIntegration](/http-api-doc.html#/Policies/post_policies__policyId__actions_activateTokenIntegration), with `policyId=my.namespace:policy-a`. + +The value of the injected subject will contain the expiration timestamp from the JWT, so the injected policy subject +`integration:temperature-observer:some-specific-audience-0815` will result in a modified policy: +```json +{ + "policyId": "my.namespace:policy-a", + "entries": { + "owner": { // unchanged ... }, + "temperature-observer": { + "subjects": { + "some-openid-connect-provider:some-user-id": { + "type": "authenticated via OpenID connect provider " + }, + "integration:temperature-observer:some-specific-audience-0815": { + "type": "added via action ", + "expiry": "2021-06-04T10:30:33Z" + } + }, + "resources": { + "thing:/features/temperature": { + "grant": ["READ"], + "revoke": [] + }, + "policy:/entries/temperature-observer/actions/activateTokenIntegration": { + "grant": ["EXECUTE"], + "revoke": [] + } + } + } + } +} +``` + +When we now have a +managed HTTP connection which [configures the `authorizationContext`](basic-connections.html#authorization) to include +the subject `integration:temperature-observer:some-specific-audience-0815` for a +[connection target](basic-connections.html#targets), this connection is allowed to publish changes to the temperature of +all things using the above policy until the `"expiry"` timestamp was reached. +Afterwards, publishing changes automatically stops, unless the action is invoked again with a JWT having a longer "exp" +time prolonging the injected policy subject. + + +## Feedback? + +Please [get in touch](feedback.html) if you have feedback or questions towards this new token based subject activation +for policies. +Or do you have other use cases in mind you might be able to solve with this feature? Please let us know. + +
+
+{% include image.html file="ditto.svg" alt="Ditto" max-width=500 %} +--
+The Eclipse Ditto team \ No newline at end of file diff --git a/documentation/src/main/resources/pages/ditto/basic-auth.md b/documentation/src/main/resources/pages/ditto/basic-auth.md index b8004e86b3..43b73dee81 100644 --- a/documentation/src/main/resources/pages/ditto/basic-auth.md +++ b/documentation/src/main/resources/pages/ditto/basic-auth.md @@ -26,6 +26,18 @@ A user who calls the HTTP API can be authenticated using two mechanisms: * A JWT issued by Google or other OpenID Connect providers as [documented in the installation/operation guide](installation-operating.html#openid-connect). +### Authenticated subjects + +Every request to one of Ditto's API is done in scope of already authenticated subjects. +This authentication may be provided via nginx (like mentioned [above](#authentication)), a +JWT or in a connection via the +configured `authorizationContext` in scope of the connection's [authorization](basic-connections.html#authorization). + +For each of the possibilities of authenticating subjects, the [command](basic-signals-command.html) or +[message](basic-messages.html) processed by Ditto will contain one or more of the "authenticated subjects" which e.g. +might be user IDs. + + ### Single sign-on (SSO) By configuring an arbitrary OpenID Connect provider (as mentioned above) it is possible for Ditto to participate in SSO diff --git a/documentation/src/main/resources/pages/ditto/basic-policy.md b/documentation/src/main/resources/pages/ditto/basic-policy.md index dc2f3c1906..93fccbca74 100644 --- a/documentation/src/main/resources/pages/ditto/basic-policy.md +++ b/documentation/src/main/resources/pages/ditto/basic-policy.md @@ -23,21 +23,38 @@ Please note, that in most cases it makes sense to grant read permission in addit ## Model specification -### API version 2 - {% include docson.html schema="jsonschema/policy.json" %} -## Who can be addressed? +## Subjects -A Subject ID must conform to one of the following rules: +Subjects in a policy define **who** gets permissions granted/revoked on the [resources](#which-resources-can-be-controlled) +of a policy entry. +Each subject ID contains a prefix defining the subject "issuer" (so which party issued the authentication) and an actual +subject, separated with a colon: +``` +: +``` -* The ID of a User defined in the nginx reverse proxy prefixed with `nginx`. -* Different JWT providers with their JWT “iss” fields - the currently supported are listed in the table below. -* OpenID Connect compliant providers - supported providers are listed at [OpenID Connect - Certified OpenID Provider Servers and Services](https://openid.net/developers/certified/) The `sub` claim and configured provider name are used in the form `:`. +The subject can be one of the following ones: +* `nginx:` - when using nginx as + [pre-authentication provider](installation-operating.html#pre-authentication) - by default enabled in the Ditto + installation's nginx +* `:` - when using another custom provider as + [pre-authentication provider](installation-operating.html#pre-authentication) which sets the + `x-ditto-pre-authenticated` HTTP header +* `google:` - in general different + JWT - the currently supported + are listed in the table: + + | Prefix | Type | Description | + |-----------|-------|---------------| + | google | jwt | A JWT issued by Google | +* `:` - + custom OpenID Connect compliant providers - supported providers are listed at + [OpenID Connect - Certified OpenID Provider Servers and Services](https://openid.net/developers/certified/) - + [can be configured](installation-operating.html#openid-connect) in Ditto defining the prefix in Ditto's config file. + The `sub` claim from the JWT and the configured provider name are used in the form `:`. -| Prefix | Type | Description | -|-----------|-------|---------------| -| google | jwt | A JWT issued by Google | ### Expiring Policy subjects @@ -54,7 +71,7 @@ When providing an `"expiry"` for a Policy subject, this timestamp is rounded up: * configured to "1h": a received "expiry" is rounded up to the next full hour (**default**) * configured to "12h": a received "expiry" is rounded up to the next half day * configured to "1d": a received "expiry" is rounded up to the next full day - * configured to "15d": a received "expiry" is rounded up to the next half month + * configured to "15d": a received "expiry" is rounded up to the next half month Once an expired subject is deleted, it will immediately no longer have access to the resources protected by the policy it was deleted from. @@ -70,9 +87,15 @@ name, e.g. for a single policy entry: ### Action activateTokenIntegration +{% include tip.html content=" + Make use of this action in order to copy your existing permissions for a pre-configured connection + (e.g. invoking an HTTP webhook) until the expiration time of the JWT the user authenticated + with passes. +" %} + When authenticated using OpenID Connect, it is possible to inject a subject into policies that expires when -the Json Web Token (JWT) expires. The form of the injected subject (i.e., token integration subject) is configurable in -the Ditto installation. +the JWT expires. +The form of the injected subject (the token integration subject) is configurable globally in the Ditto installation. A user is authorized to inject the token integration subject when granted the `EXECUTE` permission on a policy entry. The `WRITE` permission is not necessary. To activate or deactivate a token integration subject, send a `POST` @@ -101,7 +124,7 @@ request to the following HTTP routes: The injected subject pattern is configurable in Ditto and is by default: ``` {%raw%} -{{jwt:iss}}:{{policy-entry:label}}:{{jwt:sub}} +integration:{{policy-entry:label}}:{{jwt:aud}} {%endraw%} ``` @@ -283,65 +306,65 @@ Your Policy then might look like the following: The correct Policy JSON object notation would be as shown in the following code block. ```json - { - "policyId": "my.namespace:policy-a", - "entries": { - "owner": { - "subjects": { - "nginx:ditto": { - "type": "nginx basic auth user" - } - }, - "resources": { - "thing:/": { - "grant": ["READ", "WRITE"], - "revoke": [] - }, - "policy:/": { - "grant": ["READ", "WRITE"], - "revoke": [] - }, - "message:/": { - "grant": ["READ", "WRITE"], - "revoke": [] - } +{ + "policyId": "my.namespace:policy-a", + "entries": { + "owner": { + "subjects": { + "nginx:ditto": { + "type": "nginx basic auth user" } }, - "observer": { - "subjects": { - "nginx:observer-client": { - "type": "technical client" - }, - "nginx:some-users": { - "type": "a group of users" - } + "resources": { + "thing:/": { + "grant": ["READ", "WRITE"], + "revoke": [] }, - "resources": { - "thing:/features/featureX": { - "grant": ["READ"], - "revoke": [] - }, - "thing:/features/featureY": { - "grant": ["READ"], - "revoke": [] - } + "policy:/": { + "grant": ["READ", "WRITE"], + "revoke": [] + }, + "message:/": { + "grant": ["READ", "WRITE"], + "revoke": [] + } + } + }, + "observer": { + "subjects": { + "nginx:observer-client": { + "type": "technical client" + }, + "nginx:some-users": { + "type": "a group of users" } }, - "private": { - "subjects": { - "nginx:some-users": { - "type": "a group of users" - }, - "resources": { - "thing:/features/featureX/properties/location/city": { - "grant": [], - "revoke": ["READ"] - } + "resources": { + "thing:/features/featureX": { + "grant": ["READ"], + "revoke": [] + }, + "thing:/features/featureY": { + "grant": ["READ"], + "revoke": [] + } + } + }, + "private": { + "subjects": { + "nginx:some-users": { + "type": "a group of users" + }, + "resources": { + "thing:/features/featureX/properties/location/city": { + "grant": [], + "revoke": ["READ"] } } } } } +} ``` The Policy can be found: diff --git a/documentation/src/main/resources/pages/ditto/installation-operating.md b/documentation/src/main/resources/pages/ditto/installation-operating.md index 3c71ed19a8..c257e42ea2 100644 --- a/documentation/src/main/resources/pages/ditto/installation-operating.md +++ b/documentation/src/main/resources/pages/ditto/installation-operating.md @@ -50,14 +50,14 @@ HTTP API calls to Ditto may be authenticated with a reverse proxy (e.g. a nginx) * passes the authenticated username as HTTP header * ensures that this HTTP header can never be written by the end-user -By default, `pre-authentication` is **disabled** in the Ditto [gateway](architecture-services-gateway.html) services.
+By default, `pre-authentication` is **disabled** in the Ditto [gateway](architecture-services-gateway.html) services. It can however be enabled by configuring the environment variable `ENABLE_PRE_AUTHENTICATION` to the value `true`. When it is enabled, the reverse proxy has to set the HTTP header `x-ditto-pre-authenticated`.
The format of the "pre-authenticated" string is: `:`. The issuer defines which system authenticated the user and the subject contains e.g. the user-id or -name. -This string must then be used in [policies](basic-policy.html#who-can-be-addressed) as "Subject ID". +This string must then be used in [policies](basic-policy.html#subjects) as "Subject ID". Example for a nginx "proxy" configuration: ``` diff --git a/documentation/src/main/resources/pages/ditto/release_notes_120.md b/documentation/src/main/resources/pages/ditto/release_notes_120.md index 5eafbbe625..a5364cb6d9 100644 --- a/documentation/src/main/resources/pages/ditto/release_notes_120.md +++ b/documentation/src/main/resources/pages/ditto/release_notes_120.md @@ -47,7 +47,7 @@ previous expression in the function pipeline unless the condition specified by t The new HTTP `GET` resource `/whoami` may be called in order to find out which authorization subjects were resolved in the HTTP call's authentication. This can be e.g. useful to find out the used JWT subject which should be added to -[policies](basic-policy.html#who-can-be-addressed). +[policies](basic-policy.html#subjects). #### [Support using client certificate based authentication in HTTP push connections](https://github.com/eclipse/ditto/pull/695)