-
Notifications
You must be signed in to change notification settings - Fork 121
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 mirror access control #1085
base: main
Are you sure you want to change the base?
Conversation
Motivation: Sometimes you need to restrict access to specific repositories without allowing mirroring for all repositories. These repositories may contain sensitive information or code that is only available to a select group of members. Modifications: - Add `MirrorAccessController` that can allow or disallow access to the remote repositories for mirroring. - It is used to check a scheduled mirroring task or a task triggered by UI is allowed to access. - `MirrorListener.onCreate()` and `MirrorListener.onUpdate()` events are newly added. The events could be used as an extension point to detect a new remote URL pattern and integrate external systems such as a workflow or a email notification. - Add CRUD REST API for administrators - GET `/api/v1/mirror/access` to retrieve all mirror access controls. - POST `/api/v1/mirror/access` to create a mirror access controls. - and so on... - In addition to the REST API, a mirror access control can be added with `MirrorAccessController.allow(...)` API which is exposed by `PluginContext.mirrorAccessController()`. - Add `CrudRepository` abstraction to easily create and update a collection of entities in a directory. - This API would be useful especially when we implement UI-based CRUD operations. - Add mirror access control UI for administrators - CRUD are supported. Result: - Administrators can restrict access to remote repositories when mirroring. - You can receive notifications when a new mirror is created or an existing mirror is updated through `MirrorListener`.
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.
Basically looks great! Will also review the UI part. 👍
it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/MirrorAccessControlTest.java
Outdated
Show resolved
Hide resolved
it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/MirrorRunnerTest.java
Outdated
Show resolved
Hide resolved
.../src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/HasRevision.java
Show resolved
Hide resolved
it/mirror/src/test/java/com/linecorp/centraldogma/it/mirror/git/ZoneAwareMirrorTest.java
Outdated
Show resolved
Hide resolved
server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java
Outdated
Show resolved
Hide resolved
...ain/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirrorAccessController.java
Outdated
Show resolved
Hide resolved
...ain/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirrorAccessController.java
Outdated
Show resolved
Hide resolved
...ain/java/com/linecorp/centraldogma/server/internal/mirror/DefaultMirrorAccessController.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
@Override | ||
public CompletableFuture<Boolean> allow(String targetPattern, String reason, int order) { |
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.
Is there any reason that you encourage using this via PluginContext.mirrorAccessController()
instead of the REST API?
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 is possible to use REST API in a Plugin but it can be a hassle.
The plugin needs to manage an access token for each phase.
I was thinking of implementing a plugin to expose a callback as a REST API.
When the callback is invoked, a target pattern will be added to a whitelist or a blacklist via these APIs.
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 is possible to use REST API in a Plugin but it can be a hassle.
What I meant was exposing this API via MirrorAccessControlService
. Is there any reason for this?
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.
MirrorAccessController
is going to be used in a plugin to pragmatically add an access control via the API.
logger.info("Allowing the target pattern: {}", accessControl); | ||
// If there is a duplicate target pattern, the order will be considered first. | ||
// If the order is the same, the latest one will be considered first. | ||
return repository().save(accessControl, author).thenApply(unused -> true); |
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.
How can we delete this access control later? Do we just need to disallow it?
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.
Currently, it isn't supported by MirrorAccessControler
API. If necessary, should we use the UI to manage it?
Considering the current usability, I thought the design is sufficient.
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 see. I was just curious because the ID is automatically generated.
if (acl.isEmpty()) { | ||
// If there is no access control, it is allowed by default. | ||
return Streams.stream(repoUris) | ||
.collect(toImmutableMap(uri -> uri, uri -> true)); |
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.
Question) Is uri
guaranteed to be unique?
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.
Good point. uri
is not unique.
if (!allowed) { | ||
logger.debug("The mirroring from {} is not allowed. mirror: {}", | ||
m.remoteRepoUri(), m); | ||
continue; | ||
} | ||
} catch (Exception e) { | ||
logger.warn("Failed to check the access control. mirror: {}", | ||
m, e); | ||
continue; | ||
} |
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.
Question) Would it be more consistent behavior to notify MirrorListener
and log from there if needed?
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.
MirrorListener.onDisallowed()
is added to notify if a mirror is disallowed.
I didn't address the warning log for the exception because it will be raised rarely and the error may be a system-level error.
/** | ||
* Invoked when a new {@link Mirror} is created. | ||
*/ | ||
void onCreate(Mirror mirror, MirrorAccessController accessController); |
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.
Question) What do you think of only passing the mirrorId here and instead creating a MirrorContext
similar to how PluginContext
is defined which contains ProjectManager
, CommandExecutor
, etc..
CentralDogma
could maintain an internal CompositeMirrorListener
which holds this context, and pass it on to user-defined child MirrorListener
s.
This way
- we can actually handle the failure from the extra lookup in when creating/updating a mirror
- there is more freedom in
MirrorListener
to modify/read from the repository
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 agree that the extra lookup is useless for the default implementation but the overhead would not be significant.
However, it is not enough to just give a mirror ID. Since a snapshot of when it was created is needed, a revision should also be provided. Instead of adding more contexts to the API, I think the current style is not bad.
It may be possible to give more freedom, but I doubt the features are useful at this point.
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.
we can actually handle the failure from the extra lookup in when creating/updating a mirror
For more context, I imagined that we will notify users when a mirror is created/updated, but not allow
ed.
My concern wasn't really about overhead, but rather if there is a way to monitor/apply fallback logic if the callback isn't invoked due to failure from the extra IO.
(I doubt we will be looking at exception logs since we aren't actively monitoring logs at the moment)
I'm fine with the current design, but wanted to make the above point clear
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 agree that the async processing is not good from a transaction perspective. If we only pass the mirror ID, there is an advantage that MirrorListener
is always called when the commit is pushed.
Initially, I wished to design a simple API. I will consider improving this point in the future.
Sorry for causing the conflict. 😓 |
No worries. I expected it in advance. |
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.
Looks great! 👍 👍 👍
Motivation:
Sometimes you need to restrict access to specific repositories without allowing mirroring for all repositories. These repositories may contain sensitive information or code that is only available to a select group of members.
Modifications:
MirrorAccessController
that can allow or disallow access to the remote repositories for mirroring.MirrorListener.onCreate()
andMirrorListener.onUpdate()
events are newly added. The events could be used as an extension point to detect a new remote URL pattern and integrate external systems such as a workflow or an email notification./api/v1/mirror/access
to retrieve all mirror access controls./api/v1/mirror/access
to create a mirror access controls.MirrorAccessController.allow(...)
API which is exposed byPluginContext.mirrorAccessController()
.CrudRepository
abstraction to easily create and update a collection of entities in a directory.Result:
MirrorListener
.