-
Notifications
You must be signed in to change notification settings - Fork 694
feat: Implement role-based access control for Optimize to restrict access by organization roles #35347
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
Conversation
@@ -63,6 +67,9 @@ public class CCSMSecurityConfigurerAdapter extends AbstractSecurityConfigurerAda | |||
private static final Logger LOG = LoggerFactory.getLogger(CCSMSecurityConfigurerAdapter.class); | |||
|
|||
private final CCSMTokenService ccsmTokenService; | |||
|
|||
@Value("${optimize.allowed-org-roles:admin,analyst}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These roles should NOT be configurable because we don't want to allow overrides. To make it safer, can you hard-code them in this class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard-coded the roles as ALLOWED_ORG_ROLES = Arrays.asList("admin", "analyst")
and removed the configurable property. Changes are in commit 35e86a8.
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; | ||
import org.springframework.security.oauth2.jwt.Jwt; | ||
|
||
public class OptimizeRoleValidator implements OAuth2TokenValidator<Jwt> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the Optimize prefix, it's redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed OptimizeRoleValidator
to RoleValidator
and updated all references. Changes are in commit 35e86a8.
"Token does not contain required organization role for Optimize access. Required roles: %s".formatted(allowedRoles), | ||
null)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add blank line at the end of file, also check other files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added blank line at the end of RoleValidator.java. Changes are in commit 35e86a8.
Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details. Warning Firewall rules blocked me from connecting to one or more addressesI tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
35e86a8
to
6b71a35
Compare
6b71a35
to
a84be50
Compare
@@ -61,6 +64,7 @@ | |||
public class CCSMSecurityConfigurerAdapter extends AbstractSecurityConfigurerAdapter { | |||
|
|||
private static final Logger LOG = LoggerFactory.getLogger(CCSMSecurityConfigurerAdapter.class); | |||
private static final List<String> ALLOWED_ORG_ROLES = Arrays.asList("admin", "analyst"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hilalbursalii can you confirm with me what should be the allowed org roles. I can see in camunda console there's a list of roles, let me know which roles from that list should be allowed, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Below 8.8: The following roles do not have access to Optimize: Modeler, Visitor, Operations Engineer, Task User, Developer.
- 8.8+: We only have Modeler, Analyst, and Admin roles in 8.8. Again, the Modeler role should not have access.
Am I correct @ultraschuppi ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked owner role here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just added "owner" to the list of allowed roles
@RomanJRW can you help me review this PR from an Optimize perspective? Thanks 🙏
4fc95aa
to
8fa8f6a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No issues with the way this is implemented, but rather a few small questions for my understanding before we can approve
|
||
public class RoleValidator implements OAuth2TokenValidator<Jwt> { | ||
|
||
static final String ORGANIZATION_CLAIM_KEY = "https://camunda.com/orgs"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ @abremard - is this key the same in all envs or does it change for dev/int? I can't remember, it's been a while...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've replicated the code from this OrganizationValidator which parses the same claim. I'm not seeing any override of that value in the code, and I've tested on the Alex SNAPSHOT dev cluster, the decoded JWT contains "https://camunda.com/orgs"
public OAuth2TokenValidatorResult validate(final Jwt token) { | ||
final var claimValue = token.getClaims().get(ORGANIZATION_CLAIM_KEY); | ||
if (claimValue == null) { | ||
// Not all tokens contain an organization claim, only validate those that do. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ when would this be the case? I feel like have no org claims should result in a failure as I can't think of when this might be a valid scenario
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code was also based on the OrganizationValidator
code so I assumed the behavior would be the same for our cases. @lenaschoenburg do you know why a missing org claims shouldn't result in a failure in OrganizationValidator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior came from #29425:
On SaaS there is an org Authorization check performed: if the access token contains the organization id, the claim is "https://camunda.com/orgs"[*].id=ordIg, it is validated. It's skipped if not present. (client tokens lack the org, but have the cluster claim)
I don't know if it also applies to Optimize or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RomanJRW Looking at CCSMSecurityConfigurerAdapter
we don't have cluster claim checks like the single app so the behavior is different. I've updated the validator to fail when the org claim is missing
private final RoleValidator validator = new RoleValidator(allowedRoles); | ||
|
||
@Test | ||
void shouldSucceedWhenTokenHasNoOrganizationClaim() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks very deliberate here so maybe I am misunderstanding, but as above I don't see why we would want to succeed here
optimize/backend/src/test/java/io/camunda/optimize/rest/security/oauth/RoleValidatorTest.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this, LGTM 👍
…ontrol Co-authored-by: abremard <48785808+abremard@users.noreply.github.com>
…nsive tests Co-authored-by: abremard <48785808+abremard@users.noreply.github.com>
…d blank line Co-authored-by: abremard <48785808+abremard@users.noreply.github.com>
7ba7afb
to
5f6cc29
Compare
Problem
Starting from version 8.8, the app switcher no longer checks user permissions before displaying available applications. As a result, Optimize is now visible to all users, even if they lack the appropriate access rights. For instance, users with the Developer role can view and access Optimize even though they do not have the required permissions.
Solution
This PR implements role-based access control for Optimize by validating JWT organization claims during authentication. The solution:
OptimizeRoleValidator
- A new JWT validator that checks thehttps://camunda.com/orgs
claim for allowed rolesCCSMSecurityConfigurerAdapter
Implementation Details
Core Validation Logic
https://camunda.com/orgs
JWT claim containing organization dataJWT Structure Example
In this example, the user would be granted access because they have the
admin
role inorg2
.Testing
Added comprehensive unit tests covering:
Security Impact
This change ensures that only users with proper organizational roles (admin, analyst) can access Optimize, addressing the security concern where all users could access the application regardless of their roles.
Fixes #31600.
Warning
Firewall rules blocked me from connecting to one or more addresses
I tried to connect to the following addresses, but was blocked by firewall rules:
artifacts.camunda.com
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.10/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.10/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.10 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.10/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/camunda/camunda org.codehaus.plexus.classworlds.launcher.Launcher -q compile -DskipTests
(dns block)repository.jboss.org
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.10/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.10/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.10 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.10/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/camunda/camunda org.codehaus.plexus.classworlds.launcher.Launcher -q compile -DskipTests
(dns block)repository.sonatype.org
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java --enable-native-access=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED -classpath /usr/share/apache-maven-3.9.10/boot/plexus-classworlds-2.9.0.jar -Dclassworlds.conf=/usr/share/apache-maven-3.9.10/bin/m2.conf -Dmaven.home=/usr/share/apache-maven-3.9.10 -Dlibrary.jansi.path=/usr/share/apache-maven-3.9.10/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/camunda/camunda org.codehaus.plexus.classworlds.launcher.Launcher -q compile -DskipTests
(dns block)If you need me to access, download, or install something from one of these locations, you can either:
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.