As a Developer on a project, I can't push or merge into master (by default), why can I trigger a pipeline on master? If that pipeline includes a deploy, I'd be able to affect production. Sure, I can't affect the code, so I'm likely just re-deploying someone else's legitimate change. If/when we allow passing of variables to manual pipeline runs, I might be able to overwrite someone else's changes.
Proposal
For protected branches, allow only those "allowed to merge" (Usually Masters, but can be changed) to:
run manual pipelines
trigger pipelines through API
trigger specific pipeline jobs (e.g. through retry)
redeploy and rollback (technically retrying a specific job, but presented in the UI as a unique function so mentioning here so the UI is handled properly)
and perform manual actions (covered by #20261 (closed), but listed here for completeness)
Security is a fundamental part in the development cycle, as this could include deployment to production. In order to allow only authorized people to modify what is released to the public, all the interactions with pipelines run on protected branches must be limited to users that have permissions to modify them. This includes running manual pipelines, triggering pipelines through an API call, retry existing jobs and perform any manual action.
@ayufan@dimitrieh I'm not sure what the right thing to do here is. Blocking manual pipeline runs on protected branches makes sense, but is there any reason to look at one of the existing exceptions like "allow to merge" or "allow to push" and see what roles/people are allowed to do those things? Should we add a third thing, "allow to run manual pipelines"? That seems excessive.
If/when we implement #24196 (closed), we might need to reconsider this. That issue assumes pipelines are only run if you already have proper permissions for protected branches, but if someone can manually trigger a pipeline on a protected branch, but the variables and runners aren't available, it will probably end badly (but at least securely).
I think we can get away without the need for an "allow to run manual pipelines" configuration, and just say that protected branches also limit manual pipeline runs to Masters. There are times when a Developer might want to run a pipeline, and the project isn't doing CD, so it's not a security issue, but it seems like such an edge case that's not worth making an option or even a checkbox.
Also related, I think we currently let people retry pipeline jobs that run on protected branches. So even if we block running a new pipeline, someone could still re-deploy old code (accidentally) and bork production. So we'd have to block all manual pipeline triggers; for entire pipelines, for job retries, etc.
@grzesiek I don't see what limiting this abilities makes sense, and will limit any developer in his work. This would mean I can't restart any transient failures on master? Cascading effects seem not really thought through.
Also, this issue is about manually running a pipeline from scratch (possibly including custom variables), not about retrying failed jobs. The latter could have different permissions.
Quick thought on this. Functionality-wise should be simple, the toughest part would be giving feedback to the user. Why I no longer have the privilege? We might also need to hide some UI.
@bikebilly I am not completely sure if this is a security issue, provided that it's been like this from the beginning and people should be aware of that. And if this is a breaking feature change, I would prefer to think more thoroughly and make it more friendly rather than start failing silently.
A good example is #32618 (closed), which we broke and changed in a patch release.
I am not saying that we shouldn't just implement this, just want to mention that any breaking change should be carefully considered.
@dosuken123 I think you're referring to manual actions, not manual pipelines. Manual pipelines are in the project pipeline page, and click on "Run pipeline"
@bikebilly@ayufan I would think upon creation would be good enough, but well, we should also block from retrying failed jobs, for examples. Also cancelling, I think.
Pipeline schedule is a good question. I would feel we should only check upon creation, otherwise removing a person from the project might effectively remove/stop all the schedules, which is probably unexpected, and people would need to change the owner before removing previous owner.
I think it should be handled in Controller/API level (To visualize and catch ealier), thought, alternatively creating a new guard in CreatePipelineService may work. What if target branch is protected after scheduled pipeline created. We need to add the check on PipelineScheduleWorker or so.
Here are some notes before I jump into implementation.
Places we need to check
Projects::PipelinesController#create, this is using CreatePipelineService which could give proper feedback to the user
post ':id/pipeline', which is the same as above, no problems
post ':id/pipelines/:pipeline_id/retry', this is using RetryPipelineService, which unfortunately could only give generic 403 error
post ':id/pipelines/:pipeline_id/cancel', same as above
Projects::PipelinesController#retry, this doesn't give any user feedback. It might be ok if we have enough UI feedback. For example, hiding the retry button, showing tooltips that why a developer cannot retry it. (Umm, a lot of people then cannot retry failed master tests in our case)
Projects::PipelinesController#cancel, same as above
Projects::JobsController#cancel_all, same as above
Projects::JobsController#retry, this is using RetryPipelineService, which as stated above, can only give generic 403 error. The rest is the same as above
Projects::JobsController#play, same as above
post ":id/(ref/:ref/)trigger/pipeline", this is using CreateTriggerRequestService which is using CreatePipelineService. If the pipeline cannot be created, it would give 400 without a reason. However we could change this easily, because there's a message we could customize. The user of the pipeline is set to the owner of the trigger
PipelineScheduleWorker would deactivate the schedule if the underlying pipeline cannot be created. We don't have any error report for this though
Projects::PipelineSchedulesController would authorize_create_pipeline_schedule!, but we might also want to prevent it from creating for protected ref if the permission check didn't pass. Well, or maybe give a warning, or maybe it's fine if we could report at the time it was triggered
@ayufan's idea about creating the pipeline anyway but give it a reason to fail, could probably cover a lot of feedback. This could be a step forward, and we might want to set the reason why it's failing in the future anyway. However this might require UI/UX changes too, and it doesn't cover for retrying/cancelling. We're not going to fail the pipeline if someone failed to retry/cancel, and they still need to know why this is failing. We don't want to create a failed job in this case either.
Permissions we need to check
create/update_commit_status Umm... right we should think about this too, because this could potentially create a pipeline. It might not hurt, but it could be confusing
create/update_build Note that not only ProjectPolicy has this, BuildPolicy has this, too
create/update_pipeline We need to do similar stuffs as we did in BuildPolicy, duplicating the permission check in PipelinePolicy
create/update_pipeline_schedule Updating the schedule could be fine! Actually, following the same pattern as for now, we could ignore anything here, just deactivate the schedule whenever a pipeline cannot be created
@dimitrieh@bikebilly@ayufan What do you think about making protected branch/tag information more accessible?
What I am thinking here is that, we could just hide several buttons (e.g. retry, cancel) if the user has insufficient permission, but then it would not be clear why the user suddenly no longer has sufficient permission after this change.
It's also not clear a branch/tag is protected or not. If we make it clear, this pipeline/job is a protected pipeline/job, running for a protected branch/tag, then people might be more aware that they might not have sufficient permission to do this anymore.
This is not directly answering "why", but it could still be beneficial that they would be more careful for what's happening here.
I think that we are going to far. If we would focus on "fixing creation first" it would already solve a bunch of problems. I do understand a point of improving retry too, but this seems to me out of scope.
But I still agree that if we start blocking pipeline creation it should be clearly visible.
Given that I don't think that it is feasible to do for %9.3, as we don't have a way to surface that information easily, as most of our views are written in Vue, and for that reason frontend'er has to be involved.
For now I would do minimal for giving error feedback.
However I hit into a question. Could anyone answer this? @grzesiek@bikebilly@ayufan I realized that triggers might not have a corresponding owner, if they're pretty old for example, and the API is not passing the current user which makes the API call. So now I am confused which user we should be checking against.
Owner of the trigger?
Whoever uses the trigger to make the API call?
For now I implemented it as:
If there's a current user, check with it
If there's a trigger owner, check with it
If there's no users at all, only allow it if the ref is not protected (so this only breaks the old calls against protected ref)
Also, pipeline schedule would deactivate itself if the owner doesn't have the permission.
@godfat I would say that the most simple solution is just to reject pipelines triggered by ownerless trigger if a branch is protected. Relevant feedback should be presented to user so that he can take ownership over a trigger.
@ayufan It would already deactivate for now, so we would consider remove it entirely then. I think as long as we have proper error report, this won't be an issue. I want to deactivate it because (a) we're already doing so (b) as a way to inform people that this schedule doesn't work, instead of failing silently.
@dimitrieh Yes, to be more specifically, in the below two screenshots:
^^^ 80 jobs from master (protected)
^^^ from master (protected)
So that if people cannot see Retry or Cancel, they would be more aware that they might not have sufficient permission to do so, not that the buttons are just missing.
@bikebilly@dimitrieh I think that also works for me, and my only concern is that it should be consistent everywhere we're showing the branch name. We're showing branches in a lot of places, so better to make it small, indeed. I think we should even show this in dropdown menu, for example, when we're trying to run a manual pipeline, it should also tell me which branch is protected there.
@filipa Previously we don't plan to have any frontend for this, but I would like to avoid #32618 (closed) from happening again, because I think this would certainly also break a lot of people's workflow. So if we're going forward, we should at least give users feedback on why something is not working now. However, only some of the places could report this information, and some of the places would just silently fail, without giving any information. In those cases, we would like to have some proper way to report the error. I don't think there's a mockup yet though.
I think the main places we're missing reports are:
Retry and cancel
Pipeline schedule
For retry and cancel, we could just go ahead and hide the buttons. But that would also surprise people, that's why I am thinking that perhaps showing that the branch is protected would be an easier step. At least they would know the pipeline is a bit different if the buttons are missing.
As for pipeline schedule, I don't have a good idea yet. Ideally we should have a log for people to see why a schedule is not happening, I think.
@godfat@dimitrieh
I think we need both: if we keep the buttons but they don't do anything it will look like a bug, if we only remove the buttons it will look like a bug.
Cancel button is rendered if cancel_path is provided.
Retry button is rendered if retry_path is provided.
I would like to keep this logic as simple as possible :), @godfat is it possible that backend does not send theses keys if the user can't retry or cancel?
As for user info, a protected tag seams the best approach to me, main reason being:
Screen readers will be able to read it better than a labeled icon
There should be minimal frontend work here. @godfat can you please ping me on slack if you need me?
@filipa Yes it should be simple to not send cancel_path or retry_path if they can't be done, but is there a way to tell the user that why they can't be done? I thought not sending them would simply look like removing the buttons?
I would ping you when there's a clear action we could take here. It's already moved to %9.4 so I think we have time to think over it.
@grzesiek I think we're waiting for product decision for now. If we don't care about giving the error feedback, current implementation should be fine, but I strongly encourage us to have some kind of feedback before enabling this.
we have one problem with the pipelines and protected branches. In a project only masters have the right to add trigger-token's. And only masters can "take ownership" of a token. So: A trigger token will always belong to a master account (https://gitlab.com/help/ci/triggers/README.md#adding-a-new-trigger).
If i'm right your MR will limit the right to trigger a pipeline if the user is "allowed to merge". But with the current implementation of gitlab, every trigger-token belongs to a master and is allowed to merge.
So it would be great to do a "change owner" of a trigger token to some non-master account and not only a "take ownership". We currently do this with a workaround:
promote a user to master
impersonate this user
take ownership of the trigger token
demote the user to developer
But this is only a workaouround, because if you forget this ... the pipeline will always run as master.
@ulrichSchreiner If I read you correctly, if we make all developers could add their own triggers, this could be solved?
I don't know why we made that triggers are master only, but I guess after this is implemented, it makes sense to relax the permission to allow developers to make their own triggers?
@godfat Well the whole Settings are not available for a developer, and i think this is ok. I would prefer to change the owner of a trigger by a master. We have some technical accounts for such triggers, so it would be great to allow a master to set the owner of a token.
A trigger-token is not master-only but the UI-workflow leads to a situation where only master's can create and take ownership
@ulrichSchreiner Good point, so we only need to add an extra section to setup who would be the owner, is this correct? I am wondering if this would cause some weird edge cases, for example that the owner now cannot see their own stuffs, or the owner suddenly could see something they aren't supposed to see.
do you think that a mix of your proposal (when the branch name has space to get labels) and mine (padlock icon when the branch has no space) could be acceptable as a minimal change for this kind of feedback?
@bikebilly I'd rather have a consistent way of showing this information.. so let's choose visual cue to make this clear
As for what to choose. I don't think this information should perse be displayed on the branch indicator in the pipeline list. I think if we are clear on our tooltip statement of the tags presented in https://gitlab.com/gitlab-org/gitlab-ce/issues/30634#note_31836405 we should be clear.
what about for pipeline list: Pipeline runs on a protected branch
and other places: Branch is protected.
Is it possible for someone to run a pipeline on a protected branch, but which will not run (thus immediately fail) because they have insufficient rights?
Thus we need to show some sort of a failed error message?
Is it possible for someone to run a pipeline on a protected branch, but which will not run (thus immediately fail) because they have insufficient rights?
Yes, via "Run pipeline" and API. However we could provide error feedback in both cases. What we cannot show feedback would be retrying or cancelling, which we could just hide the buttons whenever they don't have permission.
about retry and cancel actions, what about disabling the buttons and add a tooltip for them that explains why they're disabled?
That would certainly work better, just that it needs frontend then :P
If you can run the pipeline, you don't need to see the badge at all. If yo cannot, the pipeline will be marked as failed and probably you're more attracted by that instead of a badge saying protected.
Do you mean that we mark the pipeline being failed if they don't have sufficient permission? I am unsure why we should do that, if we could just avoid creating the pipeline, and saying that they don't have sufficient permission? I think the current merge request already did so.
Things we currently can't give proper feedback:
retry
cancel
play
pipeline schedule (maybe no longer an issue, I am not sure)
@bikebilly I think it's still nice to have it, but yes if we have frontend and just tweak the button to give feedback, I don't think showing that the pipeline is protected would be too important here.
@filipa Do you still have capacity for this release? Or is there anyone else could help here? So what we're going to do is, we don't want to just hide the buttons for retrying or cancelling for a pipeline/job, we want to still show them, but in a disabled manner, and provide tooltip when mouseover it, telling them why the button is disabled.
How could we do this? If it's easy, maybe I could do it by myself :P
We ignore protected tag in this issue/merge request, since we decided to just show disabled buttons, and that should be clear enough for this feature.
I remembered that you said that if retry_path doesn't exist, then the button won't show. What about if we also provide disabled_retry_tooltip as the message on the tooltip, then we still show the button, but in a disabled manner with that tooltip?
Wondering if we could keep the logic of only rendering the button if retry_path is provided instead of checking for 2 things.
But I am not sure if I have a more elegant solution, to be honest.
Current implementation:
If retry_path is provided we render the button.
(In manual action, we have a playable flag for each. If this flag is false we rendered it as disabled.)
Suggestion:
I am wondering if we could follow the same pattern:
Send retry_path (it can be with the real one or an empty string)
And send also a "flag" - it is not really a flag because we need a message, it can be retry_path_disabled_message.
This would allow us to know whether to render it as disabled or not and have the message.
I suggest leaving the tooltip part out of it since it is too specific, but I guess it also works, what you prefer :)
TL;DR: If it would be possible to also send retry_path it would make the logic in the frontend a lot cleaner
@filipa Sure let's follow the same pattern, but I am not sure about the current status. Looking at the code, play_path would only be set if the job is playable. Are you saying that it's possible that play_path would be set, however playable would be false?
I could see this makes sense, and backend would have all the flexibility to decide whether we should show or disable the button.
I like retry_path_disabled_message.
Backend implementation note. Given the current implementation, we could maybe do this:
about retry and cancel actions, what about disabling the buttons and add a tooltip for them that explains why they're disabled?
@bikebilly we are currently not doing that throughout the interface. if the action is not available its hidden. Let's do it here similarly to create a consistent experience...
Ignore on blocking retry/cancel. This means that only pipeline creation would be protected, and developers could retry/cancel regardlessly (I think this is the current implementation)
Just hide the retry/cancel buttons. They won't know why they can no longer do this (I think this would be confusing, because this is a breaking change)
Provide some "protected" labels as a hint that they might not have the permission (in progress?)
Show disabled buttons (I think this works better but it's inconsistent UX)
I suppose we're only talking about feedback to users here, and that the 'protection' is enforced in any cases (it is the scope of this issue). Can you confirm?
I think that we are going to far. If we would focus on "fixing creation first" it would already solve a bunch of problems. I do understand a point of improving retry too, but this seems to me out of scope.
That's why I mentioned that we could ignore retry/cancel first. However I do agree that we should also block on retry/cancel otherwise it just seems weird (or broken).
So after all, let's decide in between:
Hide the buttons for retry/cancel
Show disabled buttons for retry/cancel
If we decided to disable buttons, we'll also need to think about when we should do this. Do we do this for all the cases? I don't actually think so. We don't have to show guests that they can't retry. So the only case I think we should show disabled buttons, would be that they usually should have the permission, but the branch/tag is protected. This logic is actually somehow weird and inconsistent, I agree with this. I might think this is just a patch to the problem, not really the answer to it.
@bikebilly Do you need a design added to the description for this? I see that @dimitrieh has provided input and feedback but that this issue is still in the UX status.
@bikebilly Thanks for checking in. The current implementation is that if it failed to create a pipeline, we would just disable the schedule, therefore the next interval won't be reached at all.
This is the same behaviour as before, just that now it's more likely to be deactivated.