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

Add OIDC authentication customizer for GitLab #1052

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from

Conversation

jhoward-lm
Copy link

Description

This PR adds an implementation of the OidcAuthenticationCustomizer service provider interface specific to GitLab. It is meant to synchronize a user's projects and roles/max access levels per project within GitLab to a Hyades instance.

Depends on stevespringett/Alpine#755.

Note

WIP: a rough to-do list with outstanding items is in a comment block in GitLabSyncTask.java

Addressed Issue

Closes DependencyTrack/hyades#1632

Additional Details

This PR is a request for early review while still in development in order to change direction if necessary.

Checklist

  • I have read and understand the contributing guidelines
  • This PR fixes a defect, and I have provided tests to verify that the fix is effective
  • This PR implements an enhancement, and I have provided tests to verify that it works as intended
  • This PR introduces changes to the database model, and I have updated the migration changelog accordingly
  • This PR introduces new or alters existing behavior, and I have updated the documentation accordingly

@jhoward-lm
Copy link
Author

Tagging for awareness:
@ashearin @lmphil @pkwiatkowski1 @Strakeln @jmayer-lm @EphraimEM

@jhoward-lm
Copy link
Author

@nscuro Would you mind doing a preliminary review and letting me know if this implementation/direction needs adjustment? Thanks!

Copy link
Member

@nscuro nscuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far. I think concurrency control of the task will become interesting.

The workflow orchestration functionality we're currently working on will provide the ability to serialize based on a concurrency group, similar to what GitHub Actions has. So this undertaking will become easier in the future, but until then we need another solution.


import static org.dependencytrack.model.ConfigPropertyConstants.GITLAB_ENABLED;

public class GitLabSyncTask implements LoggableSubscriber {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume we need a way to serialize execution of these tasks? If two or more users with overlapping GitLab project accesses login in close succession, we will encounter race conditions.

Before we go into the how (there are multiple options), can you think of any property/properties of the OIDC profile that can act as event key? i.e. what would you use as concurrency group?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in the OIDC profile comes to mind. The only thing I can think of is maybe the groups claim, if they can be locked individually, but that doesn't give us full GitLab project paths.

The name of each GitLab project as it is read from the GitLab API response can be used as well, but that isn't available in the profile

@jhoward-lm
Copy link
Author

@nscuro We noticed that changes in the alpine schema aren't yet reflected in hyades or the apiserver, so we created a changeset that seems to work. The changeset is now pushed to this PR. Do we need to open a PR in hyades to update schema.sql or anything?

/cc @pkwiatkowski1

@nscuro
Copy link
Member

nscuro commented Feb 5, 2025

The changeset is now pushed to this PR. Do we need to open a PR in hyades to update schema.sql or anything?

Leave it for now. We will add that migration in a separate PR anyway, since it involves a bit more non-SQL migration logic. You can keep the migration in your PR so you can test, but will likely need to rebase later down the road.

@jhoward-lm
Copy link
Author

@nscuro Just wanted to see if you had any ideas on a couple of bugs we're seeing. It looks like the event is somehow firing off twice, and getting a weird error about classes not being found in the CLASSPATH. For example:

2025-02-06 16:06:00,793 INFO [GitLabSyncTask] Starting GitLab sync task
2025-02-06 16:06:00,801 INFO [GitLabSyncTask] Starting GitLab sync task
2025-02-06 16:06:00,822 INFO [GitLabSyncer] Creating project example-project
2025-02-06 16:06:00,823 INFO [GitLabSyncer] Creating project example-project
2025-02-06 16:06:00,832 ERROR [Schema] An exception was thrown while adding/validating class(es) :
Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. 
Please check your specification and your CLASSPATH.
Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. 
Please check your specification and your CLASSPATH.

I'll continue debugging, just seeing if anything stands out as immediately obvious to you. Thanks!

@nscuro
Copy link
Member

nscuro commented Feb 6, 2025

@jhoward-lm For the redundant invokation, see my comment here: stevespringett/Alpine#755 (comment)

2025-02-06 16:06:00,832 ERROR [Schema] An exception was thrown while adding/validating class(es) :
Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. 
Please check your specification and your CLASSPATH.
Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. 
Please check your specification and your CLASSPATH.

Admittedly not sure, I'd have to checkout the code and see if I can reproduce it.

@jhoward-lm
Copy link
Author

@nscuro Here is the full stack trace if it helps

Full error output

2025-02-06 12:15:25,284 ERROR [Schema] An exception was thrown while adding/validating class(es) : Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. Please check your specification and your CLASSPATH.
Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. Please check your specification and your CLASSPATH.
org.datanucleus.exceptions.ClassNotResolvedException: Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. Please check your specification and your CLASSPATH.
        at org.datanucleus.ClassLoaderResolverImpl.classForName(ClassLoaderResolverImpl.java:219)
        at org.datanucleus.ClassLoaderResolverImpl.classForName(ClassLoaderResolverImpl.java:347)
        at org.datanucleus.store.rdbms.mapping.MappingManagerImpl.getMappingType(MappingManagerImpl.java:291)
        at org.datanucleus.store.rdbms.mapping.MappingManagerImpl.getDefaultJavaTypeMapping(MappingManagerImpl.java:1348)
        at org.datanucleus.store.rdbms.mapping.MappingManagerImpl.getMappingClass(MappingManagerImpl.java:829)
        at org.datanucleus.store.rdbms.mapping.MappingManagerImpl.getMapping(MappingManagerImpl.java:631)
        at org.datanucleus.store.rdbms.table.ClassTable.manageMembers(ClassTable.java:648)
        at org.datanucleus.store.rdbms.table.ClassTable.manageClass(ClassTable.java:554)
        at org.datanucleus.store.rdbms.table.ClassTable.initializeForClass(ClassTable.java:1380)
        at org.datanucleus.store.rdbms.table.ClassTable.initialize(ClassTable.java:273)
        at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.initializeClassTables(RDBMSStoreManager.java:3460)
        at org.datanucleus.store.rdbms.RDBMSStoreManager$ClassAdder.run(RDBMSStoreManager.java:3083)
        at org.datanucleus.store.rdbms.AbstractSchemaTransaction.execute(AbstractSchemaTransaction.java:118)
        at org.datanucleus.store.rdbms.RDBMSStoreManager.manageClasses(RDBMSStoreManager.java:1666)
        at org.datanucleus.store.rdbms.RDBMSStoreManager.getDatastoreClass(RDBMSStoreManager.java:670)
        at org.datanucleus.store.rdbms.query.RDBMSQueryUtils.getStatementForCandidates(RDBMSQueryUtils.java:430)
        at org.datanucleus.store.rdbms.query.JDOQLQuery.compileQueryFull(JDOQLQuery.java:911)
        at org.datanucleus.store.rdbms.query.JDOQLQuery.compileInternal(JDOQLQuery.java:377)
        at org.datanucleus.store.query.Query.executeQuery(Query.java:1965)
        at org.datanucleus.store.query.Query.executeWithMap(Query.java:1911)
        at org.datanucleus.api.jdo.JDOQuery.executeInternal(JDOQuery.java:437)
        at org.datanucleus.api.jdo.JDOQuery.executeResultUnique(JDOQuery.java:393)
        at org.dependencytrack.persistence.ProjectQueryManager.doesProjectExist(ProjectQueryManager.java:1301)
        at org.dependencytrack.persistence.QueryManager.doesProjectExist(QueryManager.java:607)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.createProjectStructure(GitLabSyncer.java:174)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.synchronize(GitLabSyncer.java:112)
        at org.dependencytrack.tasks.GitLabSyncTask.inform(GitLabSyncTask.java:78)
        at alpine.event.framework.BaseEventService.lambda$publish$0(BaseEventService.java:110)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1726)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1717)
        at java.base/java.util.concurrent.ForkJoinTask$InterruptibleTask.exec(ForkJoinTask.java:1641)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)

2025-02-06 12:15:25,284 WARN [Query] Query for candidates of org.dependencytrack.model.Project and subclasses resulted in no possible candidates : Class "org.dependencytrack.model.Classifier" was not found in the CLASSPATH. Please check your specification and your CLASSPATH.
Exception in thread "ForkJoinPool-1-worker-1" javax.jdo.JDOUserException: Don't support result clause of count(this) with resultClass of java.lang.Long
        at org.datanucleus.api.jdo.JDOAdapter.getJDOExceptionForNucleusException(JDOAdapter.java:701)
        at org.datanucleus.api.jdo.JDOQuery.executeInternal(JDOQuery.java:456)
        at org.datanucleus.api.jdo.JDOQuery.executeResultUnique(JDOQuery.java:393)
        at org.dependencytrack.persistence.ProjectQueryManager.doesProjectExist(ProjectQueryManager.java:1301)
        at org.dependencytrack.persistence.QueryManager.doesProjectExist(QueryManager.java:607)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.createProjectStructure(GitLabSyncer.java:174)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.synchronize(GitLabSyncer.java:112)
        at org.dependencytrack.tasks.GitLabSyncTask.inform(GitLabSyncTask.java:78)
        at alpine.event.framework.BaseEventService.lambda$publish$0(BaseEventService.java:110)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1726)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1717)
        at java.base/java.util.concurrent.ForkJoinTask$InterruptibleTask.exec(ForkJoinTask.java:1641)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)
NestedThrowablesStackTrace:
Don't support result clause of count(this) with resultClass of java.lang.Long
org.datanucleus.exceptions.NucleusUserException: Don't support result clause of count(this) with resultClass of java.lang.Long
        at org.datanucleus.store.rdbms.query.JDOQLQuery.compileInternal(JDOQLQuery.java:442)
        at org.datanucleus.store.query.Query.executeQuery(Query.java:1965)
        at org.datanucleus.store.query.Query.executeWithMap(Query.java:1911)
        at org.datanucleus.api.jdo.JDOQuery.executeInternal(JDOQuery.java:437)
        at org.datanucleus.api.jdo.JDOQuery.executeResultUnique(JDOQuery.java:393)
        at org.dependencytrack.persistence.ProjectQueryManager.doesProjectExist(ProjectQueryManager.java:1301)
        at org.dependencytrack.persistence.QueryManager.doesProjectExist(QueryManager.java:607)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.createProjectStructure(GitLabSyncer.java:174)
        at org.dependencytrack.integrations.gitlab.GitLabSyncer.synchronize(GitLabSyncer.java:112)
        at org.dependencytrack.tasks.GitLabSyncTask.inform(GitLabSyncTask.java:78)
        at alpine.event.framework.BaseEventService.lambda$publish$0(BaseEventService.java:110)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1726)
        at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute(ForkJoinTask.java:1717)
        at java.base/java.util.concurrent.ForkJoinTask$InterruptibleTask.exec(ForkJoinTask.java:1641)
        at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
        at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458)
        at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034)
        at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)

@jhoward-lm
Copy link
Author

@nscuro In src/main/resources/META-INF/persistence.xml, it looks like org.dependencytrack.model.Classifier is missing. Does it need to be added manually, or is this file generated from somewhere?

@nscuro
Copy link
Member

nscuro commented Feb 6, 2025

Adding Classifier there should not be necessary, because Classifier is only ever stored as simple String.

persistence.xml only needs to list classes that are "persistence capable" (i.e. represent a table, for a lack of better terms).

So either there's a bug in the ORM, or perhaps you have some changes locally that are not included in this PR?

for (String group : toCreate) {
LOGGER.debug("Creating project " + group);

Project existingProject = qm.getProject(group, null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find any place where the qm being used here is set. The code should currently throw a NPE. Is there some code you haven't yet pushed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I have some local code that sets the qm and some other fixes. We think the use of UnblockedEvent might have something to do with the Classifier not being found. I'll push what I have shortly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the change here acceptable?

@jhoward-lm jhoward-lm force-pushed the gitlab-integration branch 3 times, most recently from fa505bb to 6cad00c Compare February 13, 2025 19:56
jhoward-lm and others added 16 commits February 20, 2025 10:59
Signed-off-by: Patrick Kwiatkowski <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
Signed-off-by: Patrick Kwiatkowski <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
ashearin and others added 18 commits February 20, 2025 11:04
Signed-off-by: Ephraim Mensah <[email protected]>
Signed-off-by: Ephraim Mensah <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
Signed-off-by: Jonathan Howard <[email protected]>
@ashearin ashearin mentioned this pull request Feb 24, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Adding roles
6 participants