Skip to content

Commit

Permalink
Merge branch 'master' into action-output-path
Browse files Browse the repository at this point in the history
  • Loading branch information
wI2L authored Oct 23, 2024
2 parents e8a6ce3 + ddd4e45 commit f0c4e84
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 28 deletions.
92 changes: 92 additions & 0 deletions engine/api/rbac/dao_rbac_region_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,55 @@ package rbac
import (
"context"

"github.com/go-gorp/gorp"
"github.com/lib/pq"
"github.com/ovh/cds/engine/api/database/gorpmapping"
"github.com/ovh/cds/engine/gorpmapper"
"github.com/ovh/cds/sdk"
"github.com/ovh/cds/sdk/telemetry"
"github.com/rockbears/log"
)

func getAllRBACRegionProject(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) ([]rbacRegionProject, error) {
var rbacRegionProjects []rbacRegionProject
if err := gorpmapping.GetAll(ctx, db, q, &rbacRegionProjects); err != nil {
return nil, err
}
rbacRegionProjectsFiltered := make([]rbacRegionProject, 0, len(rbacRegionProjects))
for _, regionProject := range rbacRegionProjects {
isValid, err := gorpmapping.CheckSignature(regionProject, regionProject.Signature)
if err != nil {
return nil, sdk.WrapError(err, "error when checking signature for rbac_region_project %d", regionProject.ID)
}
if !isValid {
log.Error(ctx, "rbac.getAllRBACRegionProjectKeys> rbac_region_project %d data corrupted", regionProject.ID)
continue
}
rbacRegionProjectsFiltered = append(rbacRegionProjectsFiltered, regionProject)
}
return rbacRegionProjectsFiltered, nil
}

func getRBACRegionProject(ctx context.Context, db gorp.SqlExecutor, q gorpmapping.Query) (*rbacRegionProject, error) {
var dbRbacRegionProject rbacRegionProject
found, err := gorpmapping.Get(ctx, db, q, &dbRbacRegionProject)
if err != nil {
return nil, err
}
if !found {
return nil, sdk.WithStack(sdk.ErrNotFound)
}
isValid, err := gorpmapping.CheckSignature(dbRbacRegionProject, dbRbacRegionProject.Signature)
if err != nil {
return nil, sdk.WrapError(err, "error when checking signature for rbac_region_project %d", dbRbacRegionProject.ID)
}
if !isValid {
log.Error(ctx, "rbac.getAllRBACRegionProjectKeys> rbac_region_project %d data corrupted", dbRbacRegionProject.ID)
return nil, sdk.WithStack(sdk.ErrNotFound)
}
return &dbRbacRegionProject, nil
}

func insertRBACRegionProject(ctx context.Context, db gorpmapper.SqlExecutorWithTx, rbacRegionProject *rbacRegionProject) error {
if err := gorpmapping.InsertAndSign(ctx, db, rbacRegionProject); err != nil {
return err
Expand All @@ -20,3 +65,50 @@ func insertRBACRegionProject(ctx context.Context, db gorpmapper.SqlExecutorWithT

return nil
}

func loadRBACRegionProjectByRegionAndAllProjects(ctx context.Context, db gorp.SqlExecutor, regionID string) (*rbacRegionProject, error) {
q := gorpmapping.NewQuery("SELECT * FROM rbac_region_project WHERE region_id = $1 AND all_projects=true LIMIT 1").Args(regionID)
return getRBACRegionProject(ctx, db, q)
}

func HasRoleOnRegionProject(ctx context.Context, db gorp.SqlExecutor, role string, regionID string, projectKey string) (bool, error) {
ctx, next := telemetry.Span(ctx, "rbac.HasRoleOnRegionProject")
defer next()

// Check permission with flag all_projects
rbacRegionProject, err := loadRBACRegionProjectByRegionAndAllProjects(ctx, db, regionID)
if err != nil && !sdk.ErrorIs(err, sdk.ErrNotFound) {
return false, err
}

if rbacRegionProject != nil {
return true, nil
}

rbacRegionProjectKeyProject, err := loadRBACRegionProjectByProjectKey(ctx, db, projectKey)
if err != nil {
return false, err
}

rbacRegionProjectID := sdk.Int64Slice{}
for _, rrp := range rbacRegionProjectKeyProject {
rbacRegionProjectID = append(rbacRegionProjectID, rrp.RbacRegionProjectID)
}
rbacRegionProjectID.Unique()

if len(rbacRegionProjectID) == 0 {
return false, nil
}

rbacRegionProjects, err := loadRBACRegionProjectByIDs(ctx, db, role, regionID, rbacRegionProjectID)
if err != nil {
return false, err
}

return len(rbacRegionProjects) > 0, nil
}

func loadRBACRegionProjectByIDs(ctx context.Context, db gorp.SqlExecutor, role string, regionID string, IDs []int64) ([]rbacRegionProject, error) {
q := gorpmapping.NewQuery("SELECT * FROM rbac_region_project WHERE role = $1 AND region_id = $2 AND ID = ANY($3)").Args(role, regionID, pq.Int64Array(IDs))
return getAllRBACRegionProject(ctx, db, q)
}
5 changes: 5 additions & 0 deletions engine/api/rbac/dao_rbac_region_project_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ func loadRBACRegionProjectKeys(ctx context.Context, db gorp.SqlExecutor, rbacReg
}
return nil
}

func loadRBACRegionProjectByProjectKey(ctx context.Context, db gorp.SqlExecutor, key string) ([]rbacRegionProjectKey, error) {
q := gorpmapping.NewQuery("SELECT * FROM rbac_region_project_keys_project WHERE project_key = $1").Args(key)
return getAllRBACRegionProjectKeys(ctx, db, q)
}
62 changes: 38 additions & 24 deletions engine/api/v2_workflow_run_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ func (api *API) synchronizeRunResults(ctx context.Context, db gorp.SqlExecutor,
return nil
}

func computeRunJobsInterpolation(ctx context.Context, db *gorp.DbMap, store cache.Store, wref *WorkflowRunEntityFinder, run *sdk.V2WorkflowRun, rj *sdk.V2WorkflowRunJob, defaultRegion string, regionPermCache map[string]bool, wrEnqueue sdk.V2WorkflowRunEnqueue, u sdk.AuthentifiedUser) (*sdk.V2WorkflowRunJobInfo, bool) {
func computeRunJobsInterpolation(ctx context.Context, db *gorp.DbMap, store cache.Store, wref *WorkflowRunEntityFinder, run *sdk.V2WorkflowRun, rj *sdk.V2WorkflowRunJob, defaultRegion string, regionPermCache map[string]*sdk.V2WorkflowRunJobInfo, wrEnqueue sdk.V2WorkflowRunEnqueue, u sdk.AuthentifiedUser) (*sdk.V2WorkflowRunJobInfo, bool) {
runUpdated := false
if rj.Status.IsTerminated() {
return nil, false
Expand Down Expand Up @@ -770,16 +770,15 @@ func computeRunJobsInterpolation(ctx context.Context, db *gorp.DbMap, store cach
rj.Job.Region = reg
}

// Check Region permission
// Check user region right.
if rj.Region == "" {
rj.Job.Region = defaultRegion
rj.Region = defaultRegion
}
hasRight, has := regionPermCache[rj.Region]
jobInfoMsg, has := regionPermCache[rj.Region]
if !has {
var err error
hasRight, err = checkUserRegionRight(ctx, db, wrEnqueue, rj.Region, u)
jobInfoMsg, err = checkUserRegionRight(ctx, db, rj, wrEnqueue, rj.Region, u)
if err != nil {
rj.Status = sdk.V2WorkflowRunJobStatusFail
return &sdk.V2WorkflowRunJobInfo{
Expand All @@ -790,18 +789,12 @@ func computeRunJobsInterpolation(ctx context.Context, db *gorp.DbMap, store cach
Message: fmt.Sprintf("job %s: unable to check right for user %s: %v", rj.JobID, u.Username, err),
}, false
}
regionPermCache[rj.Region] = hasRight
regionPermCache[rj.Region] = jobInfoMsg
}

if !hasRight {
if jobInfoMsg != nil {
rj.Status = sdk.V2WorkflowRunJobStatusSkipped
return &sdk.V2WorkflowRunJobInfo{
WorkflowRunID: run.ID,
Level: sdk.WorkflowRunInfoLevelError,
WorkflowRunJobID: rj.ID,
IssuedAt: time.Now(),
Message: fmt.Sprintf("job %s: user %s does not have enough right on region %q", rj.JobID, u.Username, rj.Region),
}, false
return jobInfoMsg, false
}

if strings.Contains(rj.Job.RunsOn.Model, "${{") {
Expand Down Expand Up @@ -895,7 +888,7 @@ func prepareRunJobs(ctx context.Context, db *gorp.DbMap, store cache.Store, wref
runJobsInfo := make(map[string]sdk.V2WorkflowRunJobInfo)
hasToUpdateRun := false

regionPermCache := make(map[string]bool)
regionPermCache := make(map[string]*sdk.V2WorkflowRunJobInfo)

rootJobContext := sdk.WorkflowRunJobsContext{
WorkflowRunContext: sdk.WorkflowRunContext{
Expand Down Expand Up @@ -1369,32 +1362,53 @@ func computeRunStatusFromJobsStatus(ctx context.Context, db gorp.SqlExecutor, ru
}

// Check and set default region on job
func checkUserRegionRight(ctx context.Context, db gorp.SqlExecutor, wrEnqueue sdk.V2WorkflowRunEnqueue, regionName string, u sdk.AuthentifiedUser) (bool, error) {
func checkUserRegionRight(ctx context.Context, db gorp.SqlExecutor, rj *sdk.V2WorkflowRunJob, wrEnqueue sdk.V2WorkflowRunEnqueue, regionName string, u sdk.AuthentifiedUser) (*sdk.V2WorkflowRunJobInfo, error) {
ctx, next := telemetry.Span(ctx, "checkUserRegionRight")
defer next()

if !wrEnqueue.IsAdminWithMFA {
wantedRegion, err := region.LoadRegionByName(ctx, db, regionName)
if err != nil {
return false, err
}
wantedRegion, err := region.LoadRegionByName(ctx, db, regionName)
if err != nil {
return nil, err
}

// Check if project has the right to execute job on region
projectHasPerm, err := rbac.HasRoleOnRegionProject(ctx, db, sdk.RegionRoleExecute, wantedRegion.ID, rj.ProjectKey)
if err != nil {
return nil, err
}
if !projectHasPerm {
return &sdk.V2WorkflowRunJobInfo{
WorkflowRunID: rj.WorkflowRunID,
Level: sdk.WorkflowRunInfoLevelError,
WorkflowRunJobID: rj.ID,
IssuedAt: time.Now(),
Message: fmt.Sprintf("job %s: project %s is not allowed to start job on region %q", rj.JobID, rj.ProjectKey, rj.Region),
}, nil
}

if !wrEnqueue.IsAdminWithMFA {
allowedRegions, err := rbac.LoadRegionIDsByRoleAndUserID(ctx, db, sdk.RegionRoleExecute, u.ID)
if err != nil {
next()
return false, err
return nil, err
}
next()
for _, r := range allowedRegions {
if r.RegionID == wantedRegion.ID {
return true, nil
return nil, nil
}
}
} else {
return true, nil
return nil, nil
}

return false, nil
return &sdk.V2WorkflowRunJobInfo{
WorkflowRunID: rj.WorkflowRunID,
Level: sdk.WorkflowRunInfoLevelError,
WorkflowRunJobID: rj.ID,
IssuedAt: time.Now(),
Message: fmt.Sprintf("job %s: user %s does not have enough right on region %q", rj.JobID, u.Username, rj.Region),
}, nil
}

func checkJobNeeds(jobsContext sdk.JobsResultContext, jobDef sdk.V2Job) bool {
Expand Down
40 changes: 36 additions & 4 deletions engine/api/v2_workflow_run_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ func TestWorkflowTrigger1Job(t *testing.T) {
require.NoError(t, region.Insert(context.TODO(), db, &reg))
api.Config.Workflow.JobDefaultRegion = reg.Name

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))

rb := sdk.RBAC{
Name: sdk.RandomString(10),
Regions: []sdk.RBACRegion{
Expand All @@ -363,10 +365,16 @@ func TestWorkflowTrigger1Job(t *testing.T) {
Role: sdk.RegionRoleExecute,
},
},
RegionProjects: []sdk.RBACRegionProject{
{
Role: sdk.RegionRoleExecute,
AllProjects: true,
RegionID: reg.ID,
},
},
}
require.NoError(t, rbac.Insert(context.TODO(), db, &rb))

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcsServer := assets.InsertTestVCSProject(t, db, proj.ID, "github", "github")
repo := assets.InsertTestProjectRepository(t, db, proj.Key, vcsServer.ID, sdk.RandomString(10))

Expand Down Expand Up @@ -826,6 +834,8 @@ func TestWorkflowTriggerStage(t *testing.T) {
require.NoError(t, region.Insert(context.TODO(), db, &reg))
api.Config.Workflow.JobDefaultRegion = reg.Name

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))

rb := sdk.RBAC{
Name: sdk.RandomString(10),
Regions: []sdk.RBACRegion{
Expand All @@ -836,10 +846,16 @@ func TestWorkflowTriggerStage(t *testing.T) {
Role: sdk.RegionRoleExecute,
},
},
RegionProjects: []sdk.RBACRegionProject{
{
Role: sdk.RegionRoleExecute,
RBACProjectKeys: []string{proj.Key},
RegionID: reg.ID,
},
},
}
require.NoError(t, rbac.Insert(context.TODO(), db, &rb))

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcsServer := assets.InsertTestVCSProject(t, db, proj.ID, "github", "github")
repo := assets.InsertTestProjectRepository(t, db, proj.Key, vcsServer.ID, sdk.RandomString(10))

Expand Down Expand Up @@ -925,6 +941,8 @@ func TestWorkflowStageNeeds(t *testing.T) {
require.NoError(t, region.Insert(context.TODO(), db, &reg))
api.Config.Workflow.JobDefaultRegion = reg.Name

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))

rb := sdk.RBAC{
Name: sdk.RandomString(10),
Regions: []sdk.RBACRegion{
Expand All @@ -935,10 +953,16 @@ func TestWorkflowStageNeeds(t *testing.T) {
Role: sdk.RegionRoleExecute,
},
},
RegionProjects: []sdk.RBACRegionProject{
{
Role: sdk.RegionRoleExecute,
RBACProjectKeys: []string{proj.Key},
RegionID: reg.ID,
},
},
}
require.NoError(t, rbac.Insert(context.TODO(), db, &rb))

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcsServer := assets.InsertTestVCSProject(t, db, proj.ID, "github", "github")
repo := assets.InsertTestProjectRepository(t, db, proj.Key, vcsServer.ID, sdk.RandomString(10))

Expand Down Expand Up @@ -1307,6 +1331,8 @@ func TestWorkflowSkippedJob(t *testing.T) {
require.NoError(t, region.Insert(context.TODO(), db, &reg))
api.Config.Workflow.JobDefaultRegion = reg.Name

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))

rb := sdk.RBAC{
Name: sdk.RandomString(10),
Regions: []sdk.RBACRegion{
Expand All @@ -1317,10 +1343,16 @@ func TestWorkflowSkippedJob(t *testing.T) {
Role: sdk.RegionRoleExecute,
},
},
RegionProjects: []sdk.RBACRegionProject{
{
RegionID: reg.ID,
RBACProjectKeys: []string{proj.Key},
Role: sdk.RegionRoleExecute,
},
},
}
require.NoError(t, rbac.Insert(context.TODO(), db, &rb))

proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcsServer := assets.InsertTestVCSProject(t, db, proj.ID, "github", "github")
repo := assets.InsertTestProjectRepository(t, db, proj.Key, vcsServer.ID, sdk.RandomString(10))

Expand Down
10 changes: 10 additions & 0 deletions engine/worker/internal/handler_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ func cachePullHandler(ctx context.Context, wk *CurrentWorker) http.HandlerFunc {
writeError(w, req, err)
return
}
//close archive before removing it
if err := archive.Close(); err != nil {
err = sdk.Error{
Message: fmt.Sprintf("worker cache pull > unable to close archive %s: %v", dest, err),
Status: http.StatusInternalServerError,
}
log.Error(ctx, "%v", err)
writeError(w, req, err)
return
}
if err := wkDirFS.Remove(dest); err != nil {
e := sdk.Error{
Message: "unable to remove worker cache archive: " + err.Error(),
Expand Down
4 changes: 4 additions & 0 deletions tests/lib/v2_create_project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ steps:
all_users: true
organizations: [default]
region: {{.input.cds_region}}
region_projects:
- role: execute
region: {{.input.cds_region}}
projects: [{{.input.cds_project}}]
globals:
- role: manage-permission
users: [{{.cds_user.username}}]
Expand Down

0 comments on commit f0c4e84

Please sign in to comment.