Create eval-jobset role and guard /api/push route

(cherry picked from commit f730433789)
This commit is contained in:
Martin Weinelt 2024-08-27 17:00:00 +02:00
parent 88d9898588
commit bc6f08d233
9 changed files with 65 additions and 7 deletions

View file

@ -208,7 +208,8 @@ Example configuration:
<role_mapping> <role_mapping>
# Make all users in the hydra_admin group Hydra admins # Make all users in the hydra_admin group Hydra admins
hydra_admin = admin hydra_admin = admin
# Allow all users in the dev group to restart jobs and cancel builds # Allow all users in the dev group to eval jobsets, restart jobs and cancel builds
dev = eval-jobset
dev = restart-jobs dev = restart-jobs
dev = cancel-build dev = cancel-build
</role_mapping> </role_mapping>

View file

@ -95,6 +95,7 @@ sub get_legacy_ldap_config {
"hydra_bump-to-front" => [ "bump-to-front" ], "hydra_bump-to-front" => [ "bump-to-front" ],
"hydra_cancel-build" => [ "cancel-build" ], "hydra_cancel-build" => [ "cancel-build" ],
"hydra_create-projects" => [ "create-projects" ], "hydra_create-projects" => [ "create-projects" ],
"hydra_eval-jobset" => [ "eval-jobset" ],
"hydra_restart-jobs" => [ "restart-jobs" ], "hydra_restart-jobs" => [ "restart-jobs" ],
}, },
}; };
@ -159,6 +160,7 @@ sub valid_roles {
"bump-to-front", "bump-to-front",
"cancel-build", "cancel-build",
"create-projects", "create-projects",
"eval-jobset",
"restart-jobs", "restart-jobs",
]; ];
} }

View file

@ -248,19 +248,24 @@ sub push : Chained('api') PathPart('push') Args(0) {
foreach my $s (@jobsets) { foreach my $s (@jobsets) {
my ($p, $j) = parseJobsetName($s); my ($p, $j) = parseJobsetName($s);
my $jobset = $c->model('DB::Jobsets')->find($p, $j); my $jobset = $c->model('DB::Jobsets')->find($p, $j);
requireEvalJobsetPrivileges($c, $jobset->project);
next unless defined $jobset && ($force || ($jobset->project->enabled && $jobset->enabled)); next unless defined $jobset && ($force || ($jobset->project->enabled && $jobset->enabled));
triggerJobset($self, $c, $jobset, $force); triggerJobset($self, $c, $jobset, $force);
} }
my @repos = split /,/, ($c->request->query_params->{repos} // ""); my @repos = split /,/, ($c->request->query_params->{repos} // "");
foreach my $r (@repos) { foreach my $r (@repos) {
triggerJobset($self, $c, $_, $force) foreach $c->model('DB::Jobsets')->search( my @jobsets = $c->model('DB::Jobsets')->search(
{ 'project.enabled' => 1, 'me.enabled' => 1 }, { 'project.enabled' => 1, 'me.enabled' => 1 },
{ {
join => 'project', join => 'project',
where => \ [ 'exists (select 1 from JobsetInputAlts where project = me.project and jobset = me.name and value = ?)', [ 'value', $r ] ], where => \ [ 'exists (select 1 from JobsetInputAlts where project = me.project and jobset = me.name and value = ?)', [ 'value', $r ] ],
order_by => 'me.id DESC' order_by => 'me.id DESC'
}); });
foreach my $jobset (@jobsets) {
requireEvalJobsetPrivileges($c, $jobset->project);
triggerJobset($self, $c, $jobset, $force)
}
} }
$self->status_ok( $self->status_ok(

View file

@ -15,6 +15,7 @@ our @EXPORT = qw(
forceLogin requireUser requireProjectOwner requireRestartPrivileges requireAdmin requirePost isAdmin isProjectOwner forceLogin requireUser requireProjectOwner requireRestartPrivileges requireAdmin requirePost isAdmin isProjectOwner
requireBumpPrivileges requireBumpPrivileges
requireCancelBuildPrivileges requireCancelBuildPrivileges
requireEvalJobsetPrivileges
trim trim
getLatestFinishedEval getFirstEval getLatestFinishedEval getFirstEval
paramToList paramToList
@ -186,6 +187,27 @@ sub isProjectOwner {
defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username })); defined $c->model('DB::ProjectMembers')->find({ project => $project, userName => $c->user->username }));
} }
sub hasEvalJobsetRole {
my ($c) = @_;
return $c->user_exists && $c->check_user_roles("eval-jobset");
}
sub mayEvalJobset {
my ($c, $project) = @_;
return
$c->user_exists &&
(isAdmin($c) ||
hasEvalJobsetRole($c) ||
isProjectOwner($c, $project));
}
sub requireEvalJobsetPrivileges {
my ($c, $project) = @_;
requireUser($c);
accessDenied($c, "Only the project members, administrators, and accounts with eval-jobset privileges can perform this operation.")
unless mayEvalJobset($c, $project);
}
sub hasCancelBuildRole { sub hasCancelBuildRole {
my ($c) = @_; my ($c) = @_;
return $c->user_exists && $c->check_user_roles('cancel-build'); return $c->user_exists && $c->check_user_roles('cancel-build');

View file

@ -91,6 +91,7 @@
[% INCLUDE roleoption mutable=mutable role="restart-jobs" %] [% INCLUDE roleoption mutable=mutable role="restart-jobs" %]
[% INCLUDE roleoption mutable=mutable role="bump-to-front" %] [% INCLUDE roleoption mutable=mutable role="bump-to-front" %]
[% INCLUDE roleoption mutable=mutable role="cancel-build" %] [% INCLUDE roleoption mutable=mutable role="cancel-build" %]
[% INCLUDE roleoption mutable=mutable role="eval-jobset" %]
</p> </p>
</div> </div>
</div> </div>

View file

@ -57,6 +57,7 @@ subtest "getLDAPConfig" => sub {
"hydra_cancel-build" => [ "cancel-build" ], "hydra_cancel-build" => [ "cancel-build" ],
"hydra_create-projects" => [ "create-projects" ], "hydra_create-projects" => [ "create-projects" ],
"hydra_restart-jobs" => [ "restart-jobs" ], "hydra_restart-jobs" => [ "restart-jobs" ],
"hydra_eval-jobset" => [ "eval-jobset" ],
} }
}, },
"The empty file and set env var make legacy mode active." "The empty file and set env var make legacy mode active."
@ -177,6 +178,7 @@ subtest "get_legacy_ldap_config" => sub {
"hydra_cancel-build" => [ "cancel-build" ], "hydra_cancel-build" => [ "cancel-build" ],
"hydra_create-projects" => [ "create-projects" ], "hydra_create-projects" => [ "create-projects" ],
"hydra_restart-jobs" => [ "restart-jobs" ], "hydra_restart-jobs" => [ "restart-jobs" ],
"hydra_eval-jobset" => [ "eval-jobset" ],
} }
}, },
"Legacy, default role maps are applied." "Legacy, default role maps are applied."

View file

@ -22,9 +22,24 @@ sub is_json {
} }
my $ctx = test_context(); my $ctx = test_context();
Catalyst::Test->import('Hydra'); Catalyst::Test->import('Hydra');
# Create a user to log in to
my $user = $ctx->db->resultset('Users')->create({ username => 'alice', emailaddress => 'alice@example.com', password => '!' });
$user->setPassword('foobar');
$user->userroles->update_or_create({ role => 'admin' });
# Login and save cookie for future requests
my $req = request(POST '/login',
Referer => 'http://localhost/',
Content => {
username => 'alice',
password => 'foobar'
}
);
is($req->code, 302, "The login redirects");
my $cookie = $req->header("set-cookie");
my $finishedBuilds = $ctx->makeAndEvaluateJobset( my $finishedBuilds = $ctx->makeAndEvaluateJobset(
expression => "one-job.nix", expression => "one-job.nix",
build => 1 build => 1
@ -109,7 +124,10 @@ subtest "/api/push" => sub {
my $jobsetName = $jobset->name; my $jobsetName = $jobset->name;
is($jobset->forceeval, undef, "The existing jobset is not set to be forced to eval"); is($jobset->forceeval, undef, "The existing jobset is not set to be forced to eval");
my $response = request(POST "/api/push?jobsets=$projectName:$jobsetName&force=1"); my $response = request(POST "/api/push?jobsets=$projectName:$jobsetName&force=1",
Cookie => $cookie,
Referer => 'http://localhost/',
);
ok($response->is_success, "The API enpdoint for triggering jobsets returns 200."); ok($response->is_success, "The API enpdoint for triggering jobsets returns 200.");
my $data = is_json($response); my $data = is_json($response);
@ -128,7 +146,10 @@ subtest "/api/push" => sub {
print STDERR $repo; print STDERR $repo;
my $response = request(POST "/api/push?repos=$repo&force=1"); my $response = request(POST "/api/push?repos=$repo&force=1",
Cookie => $cookie,
Referer => 'http://localhost/',
);
ok($response->is_success, "The API enpdoint for triggering jobsets returns 200."); ok($response->is_success, "The API enpdoint for triggering jobsets returns 200.");
my $data = is_json($response); my $data = is_json($response);

View file

@ -24,6 +24,7 @@ $ldap->add_group("hydra_create-projects", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_eval-jobset", $users->{"many_roles"}->{"username"});
my $hydra_ldap_config = "${\$ldap->tmpdir()}/hydra_ldap_config.yaml"; my $hydra_ldap_config = "${\$ldap->tmpdir()}/hydra_ldap_config.yaml";
LDAPContext::write_file($hydra_ldap_config, <<YAML); LDAPContext::write_file($hydra_ldap_config, <<YAML);
@ -68,7 +69,7 @@ subtest "Valid login attempts" => sub {
unrelated => [], unrelated => [],
admin => ["admin"], admin => ["admin"],
not_admin => [], not_admin => [],
many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build" ], many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build", "eval-jobset" ],
); );
for my $username (keys %users_to_roles) { for my $username (keys %users_to_roles) {
my $user = $users->{$username}; my $user = $users->{$username};

View file

@ -24,6 +24,7 @@ $ldap->add_group("hydra_create-projects", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_restart-jobs", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_bump-to-front", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"}); $ldap->add_group("hydra_cancel-build", $users->{"many_roles"}->{"username"});
$ldap->add_group("hydra_eval-jobset", $users->{"many_roles"}->{"username"});
my $ctx = test_context( my $ctx = test_context(
@ -76,10 +77,12 @@ my $ctx = test_context(
hydra_cancel-build = cancel-build hydra_cancel-build = cancel-build
hydra_bump-to-front = bump-to-front hydra_bump-to-front = bump-to-front
hydra_restart-jobs = restart-jobs hydra_restart-jobs = restart-jobs
hydra_eval-jobset = eval-jobset
hydra_one_group_many_roles = create-projects hydra_one_group_many_roles = create-projects
hydra_one_group_many_roles = cancel-build hydra_one_group_many_roles = cancel-build
hydra_one_group_many_roles = bump-to-front hydra_one_group_many_roles = bump-to-front
hydra_one_group_many-roles = eval-jobset
</role_mapping> </role_mapping>
</ldap> </ldap>
CFG CFG
@ -92,7 +95,7 @@ subtest "Valid login attempts" => sub {
unrelated => [], unrelated => [],
admin => ["admin"], admin => ["admin"],
not_admin => [], not_admin => [],
many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build" ], many_roles => [ "create-projects", "restart-jobs", "bump-to-front", "cancel-build", "eval-jobset" ],
many_roles_one_group => [ "create-projects", "bump-to-front", "cancel-build" ], many_roles_one_group => [ "create-projects", "bump-to-front", "cancel-build" ],
); );
for my $username (keys %users_to_roles) { for my $username (keys %users_to_roles) {