Skip to content

Commit

Permalink
Pass plan policy outputs to access policy before apply (#777)
Browse files Browse the repository at this point in the history
* Pass plan policy output to access policy; run plan+policy before apply
---------

Co-authored-by: motatoes <[email protected]>
  • Loading branch information
ZIJ and motatoes authored Nov 17, 2023
1 parent 3387dba commit 2152e71
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 60 deletions.
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1091,8 +1091,6 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xanzy/go-gitlab v0.93.2 h1:kNNf3BYNYn/Zkig0B89fma12l36VLcYSGu7OnaRlRDg=
github.com/xanzy/go-gitlab v0.93.2/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
github.com/xanzy/go-gitlab v0.94.0 h1:GmBl2T5zqUHqyjkxFSvsT7CbelGdAH/dmBqUBqS+4BE=
github.com/xanzy/go-gitlab v0.94.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
Expand Down
15 changes: 13 additions & 2 deletions pkg/core/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ type Provider interface {
}

type Checker interface {
CheckAccessPolicy(ciService orchestrator.OrgService, prService *orchestrator.PullRequestService, SCMOrganisation string, SCMrepository string, projectname string, command string, prNumber *int, requestedBy string) (bool, error)
CheckPlanPolicy(SCMrepository string, projectname string, planOutput string) (bool, []string, error)
// TODO refactor arguments - use AccessPolicyContext
CheckAccessPolicy(ciService orchestrator.OrgService, prService *orchestrator.PullRequestService, SCMOrganisation string, SCMrepository string, projectName string, command string, prNumber *int, requestedBy string, planPolicyViolations []string) (bool, error)
CheckPlanPolicy(SCMrepository string, SCMOrganisation string, projectname string, planOutput string) (bool, []string, error)
CheckDriftPolicy(SCMOrganisation string, SCMrepository string, projectname string) (bool, error)
}

type AccessPolicyContext struct {
SCMOrganisation string
SCMrepository string
projectName string
command string
prNumber *int
requestedBy string
policyViolations []string
}
59 changes: 49 additions & 10 deletions pkg/digger/digger.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package digger
import (
"errors"
"fmt"
"log"
"os"
"path"
"strings"
"time"

config "github.com/diggerhq/digger/libs/digger_config"
orchestrator "github.com/diggerhq/digger/libs/orchestrator"
"github.com/diggerhq/digger/pkg/core/backend"
Expand All @@ -18,11 +24,6 @@ import (
"github.com/diggerhq/digger/pkg/notification"
"github.com/diggerhq/digger/pkg/reporting"
"github.com/diggerhq/digger/pkg/usage"
"log"
"os"
"path"
"strings"
"time"

"github.com/dominikbraun/graph"
)
Expand Down Expand Up @@ -84,7 +85,7 @@ func RunJobs(
SCMrepository := splits[1]

for _, command := range job.Commands {
allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, &prService, SCMOrganisation, SCMrepository, job.ProjectName, command, job.PullRequestNumber, job.RequestedBy)
allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, &prService, SCMOrganisation, SCMrepository, job.ProjectName, command, job.PullRequestNumber, job.RequestedBy, []string{})

if err != nil {
return false, false, fmt.Errorf("error checking policy: %v", err)
Expand Down Expand Up @@ -145,7 +146,7 @@ func reportPolicyError(projectName string, command string, requestedBy string, r
func run(command string, job orchestrator.Job, policyChecker policy.Checker, orgService orchestrator.OrgService, SCMOrganisation string, SCMrepository string, requestedBy string, reporter core_reporting.Reporter, lock core_locking.Lock, prService orchestrator.PullRequestService, projectNamespace string, workingDir string, planStorage storage.PlanStorage, appliesPerProject map[string]bool) (string, error) {
log.Printf("Running '%s' for project '%s' (workflow: %s)\n", command, job.ProjectName, job.ProjectWorkflow)

allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, &prService, SCMOrganisation, SCMrepository, job.ProjectName, command, job.PullRequestNumber, requestedBy)
allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, &prService, SCMOrganisation, SCMrepository, job.ProjectName, command, job.PullRequestNumber, requestedBy, []string{})

if err != nil {
return "error checking policy", fmt.Errorf("error checking policy: %v", err)
Expand Down Expand Up @@ -225,7 +226,7 @@ func run(command string, job orchestrator.Job, policyChecker policy.Checker, org
} else if planPerformed {
if isNonEmptyPlan {
reportTerraformPlanOutput(reporter, projectLock.LockId(), plan)
planIsAllowed, messages, err := policyChecker.CheckPlanPolicy(SCMrepository, job.ProjectName, planJsonOutput)
planIsAllowed, messages, err := policyChecker.CheckPlanPolicy(SCMrepository, SCMOrganisation, job.ProjectName, planJsonOutput)
if err != nil {
msg := fmt.Sprintf("Failed to validate plan. %v", err)
log.Printf(msg)
Expand Down Expand Up @@ -299,8 +300,46 @@ func run(command string, job orchestrator.Job, policyChecker policy.Checker, org

return comment, fmt.Errorf(comment)
} else {

// Running plan before apply to check against access policy
// TODO only run plan if policy check present. Always running plan is slow.

planPerformed, isNonEmptyPlan, _, planJsonOutput, err := diggerExecutor.Plan()
if err != nil {
//TODO reuse executor error handling
msg := fmt.Sprintf("Failed to run digger apply command. %v", err)
log.Printf(msg)
err := prService.SetStatus(*job.PullRequestNumber, "failure", job.ProjectName+"/apply")
if err != nil {
msg := fmt.Sprintf("Failed to set PR status. %v", err)
return msg, fmt.Errorf(msg)
}
return msg, fmt.Errorf(msg)
} else if planPerformed && isNonEmptyPlan {
_, planPolicyViolations, err := policyChecker.CheckPlanPolicy(SCMrepository, SCMOrganisation, job.ProjectName, planJsonOutput)
if err != nil {
msg := fmt.Sprintf("Failed to validate plan. %v", err)
log.Printf(msg)
return msg, fmt.Errorf(msg)
}
allowedToApply, err := policyChecker.CheckAccessPolicy(orgService, &prService, SCMOrganisation, SCMrepository, job.ProjectName, command, job.PullRequestNumber, requestedBy, planPolicyViolations)
if err != nil {
msg := fmt.Sprintf("Failed to run plan policy check before apply. %v", err)
log.Printf(msg)
return msg, fmt.Errorf(msg)
}
if !allowedToApply {
msg := reportPolicyError(job.ProjectName, command, requestedBy, reporter)
log.Println(msg)
return msg, errors.New(msg)
}
}

// Running apply

applyPerformed, output, err := diggerExecutor.Apply()
if err != nil {
//TODO reuse executor error handling
log.Printf("Failed to run digger apply command. %v", err)
err := prService.SetStatus(*job.PullRequestNumber, "failure", job.ProjectName+"/apply")
if err != nil {
Expand Down Expand Up @@ -412,7 +451,7 @@ func RunJob(

for _, command := range job.Commands {

allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, nil, SCMOrganisation, SCMrepository, job.ProjectName, command, nil, requestedBy)
allowedToPerformCommand, err := policyChecker.CheckAccessPolicy(orgService, nil, SCMOrganisation, SCMrepository, job.ProjectName, command, nil, requestedBy, []string{})

if err != nil {
return fmt.Errorf("error checking policy: %v", err)
Expand Down Expand Up @@ -479,7 +518,7 @@ func RunJob(
}
return fmt.Errorf(msg)
}
planIsAllowed, messages, err := policyChecker.CheckPlanPolicy(SCMrepository, job.ProjectName, planJsonOutput)
planIsAllowed, messages, err := policyChecker.CheckPlanPolicy(SCMrepository, SCMOrganisation, job.ProjectName, planJsonOutput)
log.Printf(strings.Join(messages, "\n"))
if err != nil {
msg := fmt.Sprintf("Failed to validate plan %v", err)
Expand Down
44 changes: 27 additions & 17 deletions pkg/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/diggerhq/digger/libs/orchestrator"
"github.com/diggerhq/digger/pkg/core/policy"
"github.com/open-policy-agent/opa/rego"
"io"
"log"
"net/http"
"net/url"

"github.com/diggerhq/digger/libs/orchestrator"
"github.com/diggerhq/digger/pkg/core/policy"

// "github.com/diggerhq/digger/pkg/core/policy/AccessPolicyContext"
// TODO fix imports - publish?
"github.com/open-policy-agent/opa/rego"
)

const DefaultAccessPolicy = `
package digger
default allow = true
allow = (count(input.planPolicyViolations) == 0)
`

type DiggerHttpPolicyProvider struct {
DiggerHost string
DiggerOrganisation string
Expand All @@ -24,11 +34,11 @@ type DiggerHttpPolicyProvider struct {
type NoOpPolicyChecker struct {
}

func (p NoOpPolicyChecker) CheckAccessPolicy(_ orchestrator.OrgService, _ *orchestrator.PullRequestService, _ string, _ string, _ string, _ string, _ *int, _ string) (bool, error) {
func (p NoOpPolicyChecker) CheckAccessPolicy(_ orchestrator.OrgService, _ *orchestrator.PullRequestService, _ string, _ string, _ string, _ string, _ *int, _ string, _ []string) (bool, error) {
return true, nil
}

func (p NoOpPolicyChecker) CheckPlanPolicy(_ string, _ string, _ string) (bool, []string, error) {
func (p NoOpPolicyChecker) CheckPlanPolicy(_ string, _ string, _ string, _ string) (bool, []string, error) {
return true, nil, nil
}

Expand Down Expand Up @@ -191,7 +201,7 @@ func (p *DiggerHttpPolicyProvider) GetAccessPolicy(organisation string, repo str
if resp.StatusCode == 200 {
return content, nil
} else if resp.StatusCode == 404 {
return "", nil
return DefaultAccessPolicy, nil
} else {
return "", errors.New(fmt.Sprintf("unexpected response while fetching organisation policy: %v, code %v", content, resp.StatusCode))
}
Expand Down Expand Up @@ -252,7 +262,8 @@ type DiggerPolicyChecker struct {
PolicyProvider policy.Provider
}

func (p DiggerPolicyChecker) CheckAccessPolicy(ciService orchestrator.OrgService, prService *orchestrator.PullRequestService, SCMOrganisation string, SCMrepository string, projectName string, command string, prNumber *int, requestedBy string) (bool, error) {
// TODO refactor to use AccessPolicyContext - too many arguments
func (p DiggerPolicyChecker) CheckAccessPolicy(ciService orchestrator.OrgService, prService *orchestrator.PullRequestService, SCMOrganisation string, SCMrepository string, projectName string, command string, prNumber *int, requestedBy string, planPolicyViolations []string) (bool, error) {

policy, err := p.PolicyProvider.GetAccessPolicy(SCMOrganisation, SCMrepository, projectName)

Expand All @@ -274,12 +285,13 @@ func (p DiggerPolicyChecker) CheckAccessPolicy(ciService orchestrator.OrgService
}

input := map[string]interface{}{
"user": requestedBy,
"organisation": SCMOrganisation,
"teams": teams,
"approvals": approvals,
"action": command,
"project": projectName,
"user": requestedBy,
"organisation": SCMOrganisation,
"teams": teams,
"approvals": approvals,
"planPolicyViolations": planPolicyViolations,
"action": command,
"project": projectName,
}

if policy == "" {
Expand Down Expand Up @@ -317,10 +329,8 @@ func (p DiggerPolicyChecker) CheckAccessPolicy(ciService orchestrator.OrgService
return true, nil
}

func (p DiggerPolicyChecker) CheckPlanPolicy(SCMrepository string, projectName string, planOutput string) (bool, []string, error) {
// TODO: Get rid of organisation if its not needed
organisation := p.PolicyProvider.GetOrganisation()
policy, err := p.PolicyProvider.GetPlanPolicy(organisation, SCMrepository, projectName)
func (p DiggerPolicyChecker) CheckPlanPolicy(SCMrepository string, SCMOrganisation string, projectName string, planOutput string) (bool, []string, error) {
policy, err := p.PolicyProvider.GetPlanPolicy(SCMOrganisation, SCMrepository, projectName)
if err != nil {
return false, nil, fmt.Errorf("failed get plan policy: %v", err)
}
Expand Down
Loading

0 comments on commit 2152e71

Please sign in to comment.