Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#926: inject configurable policy subjects into policies via new added JWT evaluating policy action #945

Merged
merged 57 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c0f94d0
[#926] add ActivateSubject and ActivateSubjectResponse.
yufei-cai Dec 21, 2020
a02b599
Merge branch 'master' into feature/subject-activation
yufei-cai Dec 23, 2020
b859f81
[#926] add a command to deactivate a token subject.
yufei-cai Dec 23, 2020
f60d865
[#926] add a command to activate a subject on the policy level.
yufei-cai Dec 23, 2020
683ab71
[#926] add a command to deactivate a subject at the policy level; rel…
yufei-cai Dec 23, 2020
f595551
[#926] add events for subject activation; rename ActivateSubjectForPo…
yufei-cai Dec 24, 2020
c9d1b91
[#926] add command and event strategies for ActivateSubject.
yufei-cai Dec 24, 2020
23d261f
[#926] add command and event strategies for ActivateSubjects.
yufei-cai Dec 24, 2020
e9f270f
[#926] remove raw type usage in persistence actors.
yufei-cai Dec 26, 2020
7a13c00
[#926] add irrelevant signals to registry tests of policies made visi…
yufei-cai Dec 27, 2020
f0b601b
[#926] add SubjectDeactivated and SubjectsDeactivated events.
yufei-cai Dec 27, 2020
474f077
[#926] add command and event strategies for DeactivateSubject(s)
yufei-cai Dec 28, 2020
a2b7f19
[#926] extend authentication result to include JWT.
yufei-cai Dec 29, 2020
3fc926d
[#926] add HTTP API for activateTokenIntegration and deactivateTokenI…
yufei-cai Dec 29, 2020
0df160e
[#926] Replace issuer 'integration' by token issuer in the default to…
yufei-cai Dec 30, 2020
ca8349c
[#926] remove raw types from enforcements.
yufei-cai Dec 30, 2020
097d6c6
[#926] add policy enforcement for policy action commands.
yufei-cai Dec 30, 2020
6dc7cd7
[#926] remove raw types from preEnforcer.
yufei-cai Jan 1, 2021
e517083
[#926] make subject Id resolver of policy actions configurable.
yufei-cai Jan 1, 2021
edc5200
[#926] fix deserialization of PolicyActionFailedException; fix status…
yufei-cai Jan 2, 2021
4cf48cc
[#926] document policy token integration.
yufei-cai Jan 3, 2021
70849aa
[#926] review: moved policy actions to own package in commands
thjaeckle Jan 11, 2021
86723df
[#926] fixed command registry tests by adding action command of new p…
thjaeckle Jan 12, 2021
2a1d9c9
[#926] review: removed unnecessary action events
thjaeckle Jan 12, 2021
37b75c9
[#926] Fix default subject ID resolver class name.
yufei-cai Jan 12, 2021
b7ce02b
[#926] Remove unnecessary field subjectId from Activate- and Deactiva…
yufei-cai Jan 12, 2021
850f995
[#926] Reject activateTokenIntegration actions on entries without REA…
yufei-cai Jan 13, 2021
510640f
[#926] review: added unit test for OAuthTokenIntegrationSubjectIdFactory
thjaeckle Jan 13, 2021
01e65e2
[#926] Mention in documentation the requirement for READ permission g…
yufei-cai Jan 13, 2021
676f25d
[#926] document status 404 for policy actions.
yufei-cai Jan 13, 2021
3fa8e63
[#926] review: fixed "getResourcePath" of policy entry scoped activat…
thjaeckle Jan 13, 2021
90614b7
[#926] adjust resource keys used to authorize top level policy actions.
yufei-cai Jan 13, 2021
67f872d
[#926] review: added missing javadocs for projected cache
thjaeckle Jan 13, 2021
5c3af06
[#926] review: changed status code of repsonses to 204 - no content
thjaeckle Jan 14, 2021
9167eac
[#926] review: added check that only policy entries with a subject co…
thjaeckle Jan 14, 2021
7004aa6
[#926] Add generic TopLevelActionCommand for policies.
yufei-cai Jan 14, 2021
74b2391
[#926] Merge branch 'origin/feature/subject-activation'
yufei-cai Jan 14, 2021
538172f
[#926] fix PolicyCommandEnforcementTest.
yufei-cai Jan 14, 2021
de0a06f
[#926] review: added factor "subject ID of authenticated JWT must als…
thjaeckle Jan 15, 2021
3775fe9
use ThreadSafeDittoLoggingAdapter for connectivity ConsumerActors
thjaeckle Jan 15, 2021
db46a6c
[#926] Delete Activate- and DeactivatePolicyTokenIntegration commands…
yufei-cai Jan 15, 2021
ce859dc
[#926] adjusted OpenAPI doc wording "the -> a" subject
thjaeckle Jan 15, 2021
21fe605
[#926] adjusted documentation wording about the action activateTokenI…
thjaeckle Jan 15, 2021
1f02f79
[#926] Merge branch 'master' into feature/subject-activation
yufei-cai Jan 16, 2021
cfa9145
[#926] improve type safety of AbstractCommandStrategies.
yufei-cai Jan 18, 2021
2c1aa95
[#926] prevent random failing tests due to reordering of policy entries.
yufei-cai Jan 18, 2021
0a4d841
[#926] review: renamed TopLevelActionCommand to TopLevelPolicyActionC…
thjaeckle Jan 18, 2021
3b2163c
[#926] added possibility to use JWT claims being a jsonarray of strin…
thjaeckle Jan 20, 2021
9a6b7ad
added new Hono notification "application/vnd.eclipse-hono-device-prov…
thjaeckle Jan 20, 2021
a1f6108
[#926] moved "isApplicable" logic from strategies to PolicyActionComm…
thjaeckle Jan 21, 2021
af546e3
[#926] added Blogpost about the new policy actions feature
thjaeckle Jan 21, 2021
ffb49b9
[#926] fixed internal server error cause by non-deserializable Policy…
thjaeckle Jan 21, 2021
5df4a8b
[#926] use LinkedHashMaps and LinkedHashSets in policies model in ord…
thjaeckle Jan 21, 2021
876276c
[#926] fix copyright header year for added files which fail in licens…
thjaeckle Jan 22, 2021
8d030e8
[#926] javadoc error
thjaeckle Jan 22, 2021
3dfeb3e
[#926] fix grammar.
yufei-cai Jan 22, 2021
a492eb4
[#926] Prevent backtracking in TokenIntegrationSubjectIdFactory; fix …
yufei-cai Jan 22, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Jenkinsfile_multibranch_pipeline
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pipeline {
}
steps {
configFileProvider([configFile(fileId: 'mvn-bdc-settings', variable: 'MVN_SETTINGS')]) {
sh "mvn -s $MVN_SETTINGS clean deploy source:jar " +
sh "mvn -s $MVN_SETTINGS clean deploy javadoc:jar source:jar " +
"-T1C --batch-mode --errors " +
"-Pbuild-documentation,ditto " +
"-Drevision=${theVersion} " +
Expand Down
2 changes: 1 addition & 1 deletion deployment/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ services:
- TZ=Europe/Berlin
- INSTANCE_INDEX=1
- BIND_HOSTNAME=0.0.0.0
- ENABLE_DUMMY_AUTH=true
- ENABLE_PRE_AUTHENTICATION=true
- OPENJ9_JAVA_OPTIONS=-XX:+ExitOnOutOfMemoryError -Xtune:virtualized -Xss512k -XX:MaxRAMPercentage=80 -Dakka.coordinated-shutdown.exit-jvm=on -Dakka.cluster.shutdown-after-unsuccessful-join-seed-nodes=120s
# You may use the environment for setting the devops password
#- DEVOPS_PASSWORD=foobar
Expand Down
2 changes: 1 addition & 1 deletion deployment/docker/sandbox/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ services:
- TZ=Europe/Berlin
- INSTANCE_INDEX=1
- BIND_HOSTNAME=0.0.0.0
- ENABLE_DUMMY_AUTH=true
- ENABLE_PRE_AUTHENTICATION=true
- DEVOPS_SECURE_STATUS=false
- OPENJ9_JAVA_OPTIONS=-XX:+ExitOnOutOfMemoryError -Xtune:virtualized -Xss512k -XX:MaxRAMPercentage=80 -Dakka.coordinated-shutdown.exit-jvm=on -Dakka.cluster.shutdown-after-unsuccessful-join-seed-nodes=120s

Expand Down
2 changes: 1 addition & 1 deletion deployment/kubernetes/ditto/ditto-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ spec:
timeoutSeconds: 3
failureThreshold: 4
env:
- name: ENABLE_DUMMY_AUTH
- name: ENABLE_PRE_AUTHENTICATION
value: "true"
- name: INSTANCE_INDEX
valueFrom:
Expand Down
2 changes: 1 addition & 1 deletion deployment/openshift/ditto/ditto-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ spec:
# cpu: "" no cpu limit to avoid CFS scheduler limits see https://doc.akka.io/docs/akka/snapshot/additional/deploy.html#in-kubernetes
memory: "512Mi"
env:
- name: ENABLE_DUMMY_AUTH
- name: ENABLE_PRE_AUTHENTICATION
value: "true"
- name: INSTANCE_INDEX
valueFrom:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

## 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 <a href="#" data-toggle="tooltip" data-original-title="{{site.data.glossary.jwt}}">JWT</a>
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 <some-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 <some-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 <some-openid-connect-provider>"
},
"integration:temperature-observer:some-specific-audience-0815": {
"type": "added via action <activateTokenIntegration>",
"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.

<br/>
<br/>
{% include image.html file="ditto.svg" alt="Ditto" max-width=500 %}
--<br/>
The Eclipse Ditto team
124 changes: 124 additions & 0 deletions documentation/src/main/resources/openapi/ditto-api-2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3459,6 +3459,68 @@ paths:
$ref: '#/components/schemas/AdvancedError'
'412':
$ref: '#/components/responses/PreconditionFailed'
'/policies/{policyId}/actions/activateTokenIntegration':
post:
summary: Activate subjects for this policy derived from the token
description: |-
**This action only works when authenticated with a Json Web Token (JWT).**

Based on the authenticated token (JWT), **for each policy entry** matching those conditions:
* the authenticated token is granted the `EXECUTE` permission to perform the `activateTokenIntegration` action
* one of the subject IDs is contained in the authenticated token
* at least one `READ` permission to a `thing:/` resource path is granted

a new subject is **injected into the matched policy entry** calculated with information extracted from the
authenticated JWT.

The injected subjects expire when the JWT expires.
tags:
- Policies
parameters:
- $ref: '#/components/parameters/PolicyIdPathParam'
responses:
'204':
description: The request was successful. Subjects were injected into authorized policy entries.
'400':
description: The request could not be completed because the authentication was not performed with a JWT.
'403':
description: |-
The request could not be completed because the authenticated JWT did not have the `EXECUTE` permission on any
entries of the policy.
'404':
description: |-
The request could not be completed because no policy entry matched the following conditions:
* containing a a subject ID matching the JWT's authenticated subject
* containing a `READ` permission granted to a `thing:/` resource path
'/policies/{policyId}/actions/deactivateTokenIntegration':
post:
summary: Deactivate subjects for this policy derived from the token
description: |-
**This action only works when authenticated with a Json Web Token (JWT).**

Based on the authenticated token (JWT), **for each policy entry** matching those conditions:
* the authenticated token is granted the `EXECUTE` permission to perform the `deactivateTokenIntegration` action
* one of the subject IDs is contained in the authenticated token

the calculated subject with information extracted from the authenticated JWT is **removed
from the matched policy entry**.
tags:
- Policies
parameters:
- $ref: '#/components/parameters/PolicyIdPathParam'
responses:
'204':
description: The request was successful. Subjects were removed from authorized policy entries.
'400':
description: The request could not be completed because the authentication was not performed with a JWT.
'403':
description: |-
The request could not be completed because the authenticated JWT did not have the `EXECUTE` permission on any
entries of the policy.
'404':
description: |-
The request could not be completed because no policy entry matched the following conditions:
* containing a a subject ID matching the JWT's authenticated subject
'/policies/{policyId}/entries':
get:
summary: Retrieve the entries of a specific policy
Expand Down Expand Up @@ -3863,6 +3925,68 @@ paths:
$ref: '#/components/schemas/AdvancedError'
'412':
$ref: '#/components/responses/PreconditionFailed'
'/policies/{policyId}/entries/{label}/actions/activateTokenIntegration':
post:
summary: Activate a subject for this policy entry derived from the token
description: |-
**This action only works when authenticated with a Json Web Token (JWT).**

Based on the authenticated token (JWT), **this policy entry** is checked to match those conditions:
* the authenticated token is granted the `EXECUTE` permission to perform the `activateTokenIntegration` action
* one of the subject IDs is contained in the authenticated token
* at least one `READ` permission to a `thing:/` resource path is granted

When all conditions match, a new subject is **injected into this policy entry** calculated with information
extracted from the authenticated JWT.

The injected subjects expire when the JWT expires.
tags:
- Policies
parameters:
- $ref: '#/components/parameters/PolicyIdPathParam'
- $ref: '#/components/parameters/LabelPathParam'
responses:
'204':
description: The request was successful. The subject was injected.
'400':
description: The request could not be completed because the authentication was not performed with a JWT.
'403':
description: |-
The request could not be completed because the authenticated JWT did not have the `EXECUTE` permission on this
policy entry.
'404':
description: |-
The request could not be completed because this policy entry did not match the following conditions:
* containing a a subject ID matching the JWT's authenticated subject
* containing a `READ` permission granted to a `thing:/` resource path
'/policies/{policyId}/entries/{label}/actions/deactivateTokenIntegration':
post:
summary: Deactivate a subject for this policy entry derived from the token
description: |-
**This action only works when authenticated with a Json Web Token (JWT).**

Based on the authenticated token (JWT), **this policy entry** is checked to match those conditions:
* the authenticated token is granted the `EXECUTE` permission to perform the `deactivateTokenIntegration` action
* one of the subject IDs is contained in the authenticated token

When all conditions match, the calculated subject with information extracted from the authenticated JWT is **removed
from this policy entry**.
tags:
- Policies
parameters:
- $ref: '#/components/parameters/PolicyIdPathParam'
- $ref: '#/components/parameters/LabelPathParam'
responses:
'204':
description: The request was successful. The subject was removed.
'400':
description: The request could not be completed because the authentication was not performed with a JWT.
'403':
description: The request could not be completed because the user did not have the `EXECUTE` permission on this policy entry.
'404':
description: |-
The request could not be completed because this policy entry did not match the following conditions:
* containing a a subject ID matching the JWT's authenticated subject
'/policies/{policyId}/entries/{label}/subjects':
get:
summary: Retrieve all Subjects for a specific Label of a specific policy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,18 @@ paths:
###
'/policies/{policyId}':
$ref: "./paths/policies/policy.yml"
'/policies/{policyId}/actions/activateTokenIntegration':
$ref: "./paths/policies/activateTokenIntegration.yml"
'/policies/{policyId}/actions/deactivateTokenIntegration':
$ref: "./paths/policies/deactivateTokenIntegration.yml"
'/policies/{policyId}/entries':
$ref: "./paths/policies/entries.yml"
'/policies/{policyId}/entries/{label}':
$ref: "./paths/policies/entry.yml"
'/policies/{policyId}/entries/{label}/actions/activateTokenIntegration':
$ref: "./paths/policies/activateTokenIntegrationForEntry.yml"
'/policies/{policyId}/entries/{label}/actions/deactivateTokenIntegration':
$ref: "./paths/policies/deactivateTokenIntegrationForEntry.yml"
'/policies/{policyId}/entries/{label}/subjects':
$ref: "./paths/policies/subjects.yml"
'/policies/{policyId}/entries/{label}/subjects/{subjectId}':
Expand Down