Skip to content

Commit afcc6a1

Browse files
authored
Feat/drift detection policy (#475)
* add drift detection policy
1 parent 1ce0fa6 commit afcc6a1

File tree

5 files changed

+119
-3
lines changed

5 files changed

+119
-3
lines changed

pkg/core/policy/policy.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import "digger/pkg/ci"
55
type Provider interface {
66
GetAccessPolicy(organisation string, repository string, projectname string) (string, error)
77
GetPlanPolicy(organisation string, repository string, projectname string) (string, error)
8+
GetDriftPolicy() (string, error)
89
GetOrganisation() string
910
}
1011

1112
type Checker interface {
1213
CheckAccessPolicy(ciService ci.OrgService, SCMOrganisation string, SCMrepository string, projectname string, command string, requestedBy string) (bool, error)
1314
CheckPlanPolicy(SCMrepository string, projectname string, planOutput string) (bool, []string, error)
15+
CheckDriftPolicy(SCMOrganisation string, SCMrepository string, projectname string) (bool, error)
1416
}

pkg/digger/digger.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ func RunCommandsPerProject(
278278
}
279279

280280
case "digger drift-detect":
281-
err := runDriftDetection(projectCommands.ProjectName, requestedBy, eventName, diggerExecutor)
281+
err := runDriftDetection(policyChecker, SCMOrganisation, SCMrepository, projectCommands.ProjectName, requestedBy, eventName, diggerExecutor)
282282
if err != nil {
283283
return false, false, fmt.Errorf("failed to run drift detection. %v", err)
284284
}
@@ -393,7 +393,7 @@ func RunCommandForProject(
393393
return fmt.Errorf("failed to run digger apply command. %v", err)
394394
}
395395
case "digger drift-detect":
396-
err = runDriftDetection(commands.ProjectName, requestedBy, eventName, diggerExecutor)
396+
err = runDriftDetection(policyChecker, SCMOrganisation, SCMrepository, commands.ProjectName, requestedBy, eventName, diggerExecutor)
397397
if err != nil {
398398
return fmt.Errorf("failed to run digger drift-detect command. %v", err)
399399
}
@@ -403,12 +403,21 @@ func RunCommandForProject(
403403
return nil
404404
}
405405

406-
func runDriftDetection(projectName string, requestedBy string, eventName string, diggerExecutor execution.Executor) error {
406+
func runDriftDetection(policyChecker policy.Checker, SCMOrganisation string, SCMrepository string, projectName string, requestedBy string, eventName string, diggerExecutor execution.Executor) error {
407407
err := usage.SendUsageRecord(requestedBy, eventName, "drift-detect")
408408
if err != nil {
409409
log.Printf("Failed to send usage report. %v", err)
410410
}
411+
policyAllowed, err := policyChecker.CheckDriftPolicy(SCMOrganisation, SCMrepository, projectName)
412+
if err != nil {
413+
log.Printf("failed to check drift policy. %v", err)
414+
return err
415+
}
411416

417+
if !policyAllowed {
418+
log.Printf("skipping this drift application since drift policy does not allow it")
419+
return nil
420+
}
412421
planPerformed, nonEmptyPlan, plan, _, err := diggerExecutor.Plan()
413422
if err != nil {
414423
log.Printf("Failed to run digger plan command. %v", err)

pkg/policy/policy.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ func (p NoOpPolicyChecker) CheckPlanPolicy(_ string, _ string, _ string) (bool,
3232
return true, nil, nil
3333
}
3434

35+
func (p NoOpPolicyChecker) CheckDriftPolicy(SCMOrganisation string, SCMrepository string, projectname string) (bool, error) {
36+
return true, nil
37+
}
38+
3539
func getAccessPolicyForOrganisation(p *DiggerHttpPolicyProvider) (string, *http.Response, error) {
3640
organisation := p.DiggerOrganisation
3741
u, err := url.Parse(p.DiggerHost)
@@ -84,6 +88,32 @@ func getPlanPolicyForOrganisation(p *DiggerHttpPolicyProvider) (string, *http.Re
8488
return string(body), resp, nil
8589
}
8690

91+
func getDriftPolicyForOrganisation(p *DiggerHttpPolicyProvider) (string, *http.Response, error) {
92+
organisation := p.DiggerOrganisation
93+
u, err := url.Parse(p.DiggerHost)
94+
if err != nil {
95+
log.Fatalf("Not able to parse digger cloud url: %v", err)
96+
}
97+
u.Path = "/orgs/" + organisation + "/drift-policy"
98+
req, err := http.NewRequest("GET", u.String(), nil)
99+
if err != nil {
100+
return "", nil, err
101+
}
102+
req.Header.Add("Authorization", "Bearer "+p.AuthToken)
103+
104+
resp, err := p.HttpClient.Do(req)
105+
if err != nil {
106+
return "", nil, err
107+
}
108+
defer resp.Body.Close()
109+
110+
body, err := io.ReadAll(resp.Body)
111+
if err != nil {
112+
return "", resp, nil
113+
}
114+
return string(body), resp, nil
115+
}
116+
87117
func getAccessPolicyForNamespace(p *DiggerHttpPolicyProvider, namespace string, projectName string) (string, *http.Response, error) {
88118
// fetch RBAC policies for project from Digger API
89119
u, err := url.Parse(p.DiggerHost)
@@ -200,6 +230,20 @@ func (p *DiggerHttpPolicyProvider) GetPlanPolicy(organisation string, repo strin
200230
}
201231
}
202232

233+
func (p *DiggerHttpPolicyProvider) GetDriftPolicy() (string, error) {
234+
content, resp, err := getDriftPolicyForOrganisation(p)
235+
if err != nil {
236+
return "", err
237+
}
238+
if resp.StatusCode == 200 {
239+
return content, nil
240+
} else if resp.StatusCode == 404 {
241+
return "", nil
242+
} else {
243+
return "", errors.New(fmt.Sprintf("unexpected response while fetching organisation policy: %v, code %v", content, resp.StatusCode))
244+
}
245+
}
246+
203247
func (p *DiggerHttpPolicyProvider) GetOrganisation() string {
204248
return p.DiggerOrganisation
205249
}
@@ -330,3 +374,52 @@ func (p DiggerPolicyChecker) CheckPlanPolicy(SCMrepository string, projectName s
330374

331375
return true, []string{}, nil
332376
}
377+
378+
func (p DiggerPolicyChecker) CheckDriftPolicy(SCMOrganisation string, SCMrepository string, projectName string) (bool, error) {
379+
// TODO: Get rid of organisation if its not needed
380+
//organisation := p.PolicyProvider.GetOrganisation()
381+
policy, err := p.PolicyProvider.GetDriftPolicy()
382+
if err != nil {
383+
fmt.Printf("Error while fetching drift policy: %v", err)
384+
return false, err
385+
}
386+
387+
input := map[string]interface{}{
388+
"organisation": SCMOrganisation,
389+
"project": projectName,
390+
}
391+
392+
if policy == "" {
393+
return true, nil
394+
}
395+
396+
ctx := context.Background()
397+
fmt.Printf("DEBUG: passing the following input policy: %v ||| text: %v", input, policy)
398+
query, err := rego.New(
399+
rego.Query("data.digger.allow"),
400+
rego.Module("digger", policy),
401+
).PrepareForEval(ctx)
402+
403+
if err != nil {
404+
return false, err
405+
}
406+
407+
results, err := query.Eval(ctx, rego.EvalInput(input))
408+
if len(results) == 0 || len(results[0].Expressions) == 0 {
409+
return false, fmt.Errorf("no result found")
410+
}
411+
412+
expressions := results[0].Expressions
413+
414+
for _, expression := range expressions {
415+
decision, ok := expression.Value.(bool)
416+
if !ok {
417+
return false, fmt.Errorf("decision is not a boolean")
418+
}
419+
if !decision {
420+
return false, nil
421+
}
422+
}
423+
424+
return true, nil
425+
}

pkg/policy/policy_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func (s *DiggerExamplePolicyProvider) GetPlanPolicy(_ string, _ string, _ string
6969
return "package digger\n", nil
7070
}
7171

72+
func (s *DiggerExamplePolicyProvider) GetDriftPolicy() (string, error) {
73+
return "package digger\n", nil
74+
}
75+
7276
func (s *DiggerExamplePolicyProvider) GetOrganisation() string {
7377
return "ORGANISATIONDIGGER"
7478
}
@@ -99,6 +103,10 @@ func (s *DiggerExamplePolicyProvider2) GetPlanPolicy(_ string, _ string, _ strin
99103
return "package digger\n\ndeny[sprintf(message, [resource.address])] {\n message := \"Cannot create EC2 instances!\"\n resource := input.terraform.resource_changes[_]\n resource.change.actions[_] == \"create\"\n resource[type] == \"aws_instance\"\n}\n", nil
100104
}
101105

106+
func (s *DiggerExamplePolicyProvider2) GetDriftPolicy() (string, error) {
107+
return "package digger\n", nil
108+
}
109+
102110
func (s *DiggerExamplePolicyProvider2) GetOrganisation() string {
103111
return "ORGANISATIONDIGGER"
104112
}

pkg/utils/mocks.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ func (t MockPolicyChecker) CheckPlanPolicy(projectName string, command string, r
5252
return false, nil, nil
5353
}
5454

55+
func (t MockPolicyChecker) CheckDriftPolicy(SCMOrganisation string, SCMrepository string, projectname string) (bool, error) {
56+
return true, nil
57+
}
58+
5559
type MockPullRequestManager struct {
5660
ChangedFiles []string
5761
Teams []string

0 commit comments

Comments
 (0)