Job based security per branch - Jenkins Multibranch pipeline - security

I have a Jenkins multi-branch pipeline for building artifacts and there are branches for master, *-dev etc.
I want to enable project based security on a per branch basis, ie only allow devs to run the *-dev branch jobs of the build not any other ones because doing so would have undesirable effects.
I know there is project based security, but I didn't see any per branch. Does this exist? We are behind in updating Jenkins and are currently running Jenkins 2.46.1.
Otherwise I am thinking I might have to have a separate upstream job to call the correct branch of the downstream one and make the downstream artifact job unable to be run by devs with the privilege to do so. (This sounds like overkill).
Or is there any way to accomplish this in the branch's Jenkinsfile?

Here's some Jenkinsfile groovy that will get you close to what you want:
// return the user id that caused this build; else empty string
#NonCPS
def user_id_cause() {
def CAUSE = currentBuild.rawBuild.getCause(
hudson.model.Cause.UserIdCause.class
);
return CAUSE ? CAUSE.getUserId() : "";
}
// return all groups to which the given user id belongs
#NonCPS
def groups(USER_ID) {
return Jenkins.instance.securityRealm.loadUserByUsername(USER_ID).authorities.collect{ it.toString() };
}
...
env.USER_ID_CAUSE = user_id_cause();
if (!env.BRANCH_NAME.endsWith('-dev')) {
if (env.USER_ID_CAUSE) {
if ('jenkins_admins' in groups(env.USER_ID_CAUSE)) {
echo("INFO: user id `${env.USER_ID_CAUSE}` is in the group `jenkins_admins`.");
} else {
currentBuild.result = 'ABORTED';
error("user id `${env.USER_ID_CAUSE}` is not in the group `jenkins_admins`.");
}
}
}
Caveats:
These tricks rely heavily on API functions that require "In-process Script Approval" by a Jenkins administrator.
The above example assumes the existence of the jenkins_admins group to which privileged users belong --- your user/groups situation may be very different.
In general, playing with objects returned from Jenkins API functions should be done within #NonCPS-annotated functions --- you risk java.io.NotSerializableException otherwise.
References:
https://github.com/jenkinsci/workflow-cps-plugin/blob/master/README.md
http://javadoc.jenkins-ci.org/hudson/model/Cause.UserCause.html
http://javadoc.jenkins-ci.org/hudson/model/Run.html#getCause-java.lang.Class-
http://javadoc.jenkins.io/hudson/security/SecurityRealm.html#loadUserByUsername-java.lang.String-

Related

Intercepting an assignment to a table relation field

I created a one-to one relationship between two tables in strapi.
As an example, suppose that Bob currently has a job, say messenger, if we assign Bob’s Job to secretary, Strapi simply reassigns the new Job, without warning that Bob was already in a job
If a person is not in a current job, it’s job would be ‘none’
I’d like to forbid the reassignment of the job, if Bob was already in a job (the user would have to assign the Bob's job to ‘none’ before assigning a new job)
In strapi, what would be the right way to forbid it (checking if the current job is not ‘none’, and, if it’s the case, stopping the assignment), using a service, a controller or a lifecycle hook?
One way to handle this in Strapi would be to use a lifecycle hook. Lifecycle hooks allow you to perform specific actions at certain stages of the CRUD operations (create, update, delete) on a model. In this case, you can use the beforeUpdate hook to check if the current job is not none before allowing the assignment of a new job:
// api/person/models/Person.js
module.exports = {
lifecycles: {
// This hook will be called before updating a person
async beforeUpdate(params, data) {
// Check if the current job is not 'none'
if (params.current.job !== 'none') {
// If the current job is not 'none', throw an error
throw new Error('Cannot reassign a job to a person who already has a job');
}
}
}
};
You can also use a service or a controller to handle this logic, but using a lifecycle hook allows you to centralize this logic and keep it separate from your business logic.

what is the best way to build multiple permission group on mongodb?

I started a complex project management application and I have the challenge of building resource permissions management for different types of user profiles.
My challenge is:
User story
John is an user with a common user profile.
John creates a project in the application.
John creates several tasks and adds them to the project.
John adds an user responsible for each task.
Added users must have access to the project and the tasks to which they have been added.
John creates a specific task and adds it as a subtask to one of the project's tasks.
In this subtask John adds an user as responsible, automatically that user must have access to the subtask, the task and the project.
And at any time, John can restrict access to a project resource, such as defining that a specific user can only view tasks
The way I started.
I created a specification pattern for each use case, where I inform the variables and it returns a true or false answer.
However, I have to request for each resource and, in my opinion, this is not performative.
What I mentioned is one of the simplest cases, there are others that are more complex.
canEditTaskOnProject(): boolean {
if (!this.project) {
console.error(
`Project not provided on ${TaskPermission.name}.${this.canEditTaskOnProject.name}`
);
return false;
}
return new ProjectLeader(this.project, this.userId)
.or(new Creator(this.task, this.userId))
.or(new FullAccessTaskPermission(this.project, this.userId))
.or(new TaskResponsible(this.task, this.userId))
.or(
new RestrictTaskPermission(this.project, this.userId).and(
new Creator(this.task, this.userId).or(
new TaskResponsible(this.task, this.userId)
)
)
)
.or(
new ReadAndWriteTaskPermission(this.project, this.userId).and(
new TaskResponsible(this.task, this.userId)
)
)
.isSatisfiedBy(this.userId);
}
I would very much like suggestions from experienced people who have already done something similar. I am a beginner in the area and in the company where I work, there are no seniors.
Thank you in advance!
I found the best way using casl:
import { defineAbility } from '#casl/ability';
export default defineAbility((can, cannot) => {
can('read', 'Article');
cannot('read', 'Article', { published: false }); // inverted rule
});

Puppet - How to write yaml files based on Role/Profile method

I've added our infrastructure setup to puppet, and used roles and profiles method. Each profile resides inside a group, based on their nature. For example, Chronyd setup and Message of the day are in "base" group, nginx-related configuration is in "app" group. Also, on the roles, each profile is added to the corresponding group. For example for memcached we have the following:
class role::prod::memcache inherits role::base::debian {
include profile::app::memcache
}
The profile::app::memcached has been set up like this :
class profile::app::memcache {
service { 'memcached':
ensure => running,
enable => true,
hasrestart => true,
hasstatus => true,
}
}
and for role::base::debian I have :
class role::base::debian {
include profile::base::motd
include profile::base::chrony
}
The above structure has proved to be flexible enough for our infrastructure. Adding services and creating new roles could not been easier than this. But now I face a new problem. I've been trying to separate data from logic, write some yaml files to keep the data there, using Hiera version 5. Been looking through internet for a couple of days, but I cannot deduct how to write my hiera files based on the structure I have. I tried adding profile::base::motd to common.yaml and did a puppet lookup, it works fine, but I could not append chrony to common.yaml. Puppet lookup returns nothing with the following common.yaml contents :
---
profile::base::motd::content: This server access is restricted to authorized users only. All activities on this system are logged. Unauthorized access will be liable to prosecution.'
profile::base::chrony::servers: 'ntp.centos.org'
profile::base::chrony::service_enable: 'true'
profile::base::chrony::service_ensure: 'running'
Motd lookup works fine. But the rest, no luck. puppet lookup profile::base::chrony::servers returns with no output. Don't know what I'm missing here. Would really appreciate the community's help on this one.
Also, using hiera, is the following enough code for a service puppet file?
class profile::base::motd {
class { 'motd':
}
}
PS : I know I can add yaml files inside modules to keep the data, but I want my .yaml files to reside in one place (e.g. $PUPPET_HOME/environment/production/data) so I can manage the code with git.
The issue was that in init.pp file inside the puppet module itself, the variable $content was assigned a value. Removing the value fixed the problem.

Microsoft.TeamFoundation.Build.WebApi Get Build Status Launched by PR policy

In our pipeline we programmatically create a pull request (PR). The branch being merged into has a policy on it that launches a build. This build takes a variable amount of time. I need to query the build status until it is complete (or long timeout) so that I can complete the PR, and clean up the temp branch.
I am trying to figure out how to get the build that was kicked off by the PR so that I can inspect the status by using Microsoft.TeamFoundation.Build.WebApi, but all overloads of BuildHttpClientBase.GetBuildAsync require a build Id which I don't have. I would like to avoid using the Azure Build REST API. Does anyone know how I might get the Build kicked off by the PR without the build ID using BuildHttpClientBase?
Unfortunately the documentation doesn't offer a lot of detail about functionality.
Answering the question you asked:
Finding a call that provides the single deterministic build id for a pull request doesn't seem to be very readily available.
As mentioned, you can use BuldHttpClient.GetBuildsAsync() to filter builds based on branch, repository, requesting user and reason.
Adding the BuildReason.PullRequest value in the request is probably redundant according to the branch you will need to pass.
var pr = new GitPullRequest(); // the PR you've received after creation
var requestedFor = pr.CreatedBy.DisplayName;
var repo = pr.Repository.Id.ToString();
var branch = $"refs/pull/{pr.PullRequestId}/merge";
var reason = BuildReason.PullRequest;
var buildClient = c.GetClient<BuildHttpClient>();
var blds = await buildClient.GetBuildsAsync("myProject",
branchName: branch,
repositoryId: repo,
requestedFor: requestedFor,
reasonFilter: reason,
repositoryType: "TfsGit");
In your question you mentioned wanting the build (singular) for the pull request, which implies that you only have one build definition acting as the policy gate. This method can return multiple Builds based on the policy configurations on your target branch. However, if that were your setup, it would seem logical that your question would then be asking for all those related builds for which you would wait to complete the PR.
I was looking into Policy Evaluations to see if there was a more straight forward way to get the id of the build being run via policy, but I haven't been able to format the request properly as per:
Evaluations are retrieved using an artifact ID which uniquely identifies the pull request. To generate an artifact ID for a pull request, use this template:
vstfs:///CodeReview/CodeReviewId/{projectId}/{pullRequestId}
Even using the value that is returned in the artifactId field on the PR using the GetById method results in a Doesn't exist or Don't have access response, so if someone else knows how to use this method and if it gives exact build ids being evaluated for the policy configurations, I'd be glad to hear it.
An alternative to get what you actually desire
It sounds like the only use you have for the branch policy is to run a "gate build" before completing the merge.
Why not create the PR with autocomplete.
Name - autoCompleteSetBy
Type - IdentityRef
Description - If set, auto-complete is enabled for this pull request and this is the identity that enabled it.
var me = new IdentityRef(); // you obviously need to populate this with real values
var prClient = connection.GetClient<GitHttpClient>();
await prClient.CreatePullRequestAsync(new GitPullRequest()
{
CreatedBy = me,
AutoCompleteSetBy = me,
Commits = new GitCommitRef[0],
SourceRefName = "feature/myFeature",
TargetRefName = "master",
Title = "Some good title for my PR"
},
"myBestRepository",
true);

Twilio Taskrouter: how to implement a "do not contact" list of WorkerSids in Workflow Configuration?

This question is similar to this one I previously asked, in that I want the task to perform a Target Worker Expression check on a list of WorkerSids that I've added as one of the task's attributes. But I think this problem is different enough to warrant its own question.
My goal is to associate a "do not contact" list of WorkerSids with a Task; these are workers who should not be assigned the task (maybe the customer previously had a bad interaction with them).
I have the following workflow configuration:
{
"task_routing":{
"filters":[
{
"filter_friendly_name":"don't call self",
"expression":"1==1",
"targets":[
{
"queue":queueSid,
"expression":"(task.caller!=worker.contact_uri) and (worker.sid not in task.do_not_contact)",
"skip_if": "workers.available == 0"
},
{
"queue":automaticQueueSid
}
]
}
],
"default_filter":{
"queue":queueSid
}
}
}
When I create a task, checking the Twilio Console, I can see that the task has the following attributes:
{"from_country":"US","do_not_contact":["WORKER_SID1_HERE","WORKER_SID_2_HERE"],
... bunch of other attributes...
}
So I know that the task has successfully been assigned the array of WorkerSids as one of its attributes.
There is only one worker who is Idle and whose attributes match the queueSid TaskQueue. That worker's SID is WORKER_SID1_HERE, so the only available worker is ineligible to receive the task reservation. So what should happen is that the first target expression worker.sid not in task.do_not_contact returns false, and the task falls through to the automaticQueueSid TaskQueue.
Instead, the task remains in queueSid unassigned. The following sequence of Taskrouter events are logged:
task-queue.entered
Task TASK_SID entered TaskQueue QUEUESID_QUEUENAME
task.created
Task TASK_SID created
workflow.target-matched
Task TASK_SID matched a workflow target
workflow.entered
Task TASK_SID entered Workflow WORKFLOW_NAME
What do I need to change to get the desired workflow behavior?
Changing the skip_if to
"skip_if": "1==1"
solved the problem.
Per Twilio developer support, the worker.sid not in task.do_not_contact returns true for workers who are unavailable but are also not in do_not_contact, so the target expression still returns a set of workers, and then the "skip_if": "workers.available==0" returns false because technically there is one "available" worker--the one who is ineligible due to the do_not_contact list.
What's needed is for the skip_if to always return true, so when the first target processes the task without assigning it, the skip_if then passes it to the next target, as discussed in Taskrouter Workflow documentation:
"TaskRouter will only skip a routing step in a Workflow if:
No Reservations are immediately created when a Task enters the routing step
The Skip Timeout expression evaluates to true"

Resources