-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathpolicy.go
More file actions
206 lines (177 loc) · 7.71 KB
/
Copy pathpolicy.go
File metadata and controls
206 lines (177 loc) · 7.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package domain
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/mcuadros/go-lookup"
)
const (
ApproversKeyResource = "$resource"
)
var (
ErrInvalidConditionField = errors.New("unable to parse condition's field")
)
type ApprovalStepStrategy string
const (
ApprovalStepStrategyAuto ApprovalStepStrategy = "auto"
ApprovalStepStrategyManual ApprovalStepStrategy = "manual"
)
// MatchCondition is for determining the requirement of the condition
type MatchCondition struct {
Eq interface{} `json:"eq" yaml:"eq"`
}
// Condition gets evaluated to determine the approval step resolution whether it is success or failed
type Condition struct {
Field string `json:"field" yaml:"field" validate:"required"`
Match *MatchCondition `json:"match" yaml:"match" validate:"required"`
}
func (c *Condition) IsMatch(a *Appeal) (bool, error) {
if strings.HasPrefix(c.Field, ApproversKeyResource) {
jsonString, err := json.Marshal(a.Resource)
if err != nil {
return false, err
}
var resourceMap map[string]interface{}
if err := json.Unmarshal(jsonString, &resourceMap); err != nil {
return false, err
}
path := strings.TrimPrefix(c.Field, fmt.Sprintf("%s.", ApproversKeyResource))
value, err := lookup.LookupString(resourceMap, path)
if err != nil {
return false, err
}
expectedValue := c.Match.Eq
return value.Interface() == expectedValue, nil
}
return false, fmt.Errorf("evaluating field: %v: %v", c.Field, ErrInvalidConditionField)
}
// Step is an individual process within an approval flow
type Step struct {
// Name used as the step identifier
Name string `json:"name" yaml:"name" validate:"required"`
// Description tells more details about the step
Description string `json:"description" yaml:"description"`
// AllowFailed lets the approval flow continue to the next step even the current step is rejected.
// If the last step has AllowFailed equal to true, and it's getting rejected,
// the appeal status will resolve as approved or success.
AllowFailed bool `json:"allow_failed" yaml:"allow_failed"`
// When is an Expression that determines whether the step should be evaluated or it can be skipped at the beginning.
// If it evaluates to be falsy, the step will automatically skipped. Otherwise, step become pending/blocked (normal).
//
// Accessible parameters:
// $appeal = Appeal object
When string `json:"when,omitempty" yaml:"when,omitempty"`
// Strategy defines if the step requires manual approval or not
Strategy ApprovalStepStrategy `json:"strategy" yaml:"strategy" validate:"required,oneof=auto manual"`
// RejectionReason message fills `Approval.Reason` if the approval step gets rejected based on `ApproveIf` expression.
RejectionReason string `json:"rejection_reason" yaml:"rejection_reason"`
// Approvers is an Expression that if the evaluation returns string or []string that contains email address of the approvers.
// If human approval (manual) is required, use this field.
//
// Accessible parameters:
// $appeal = Appeal object
Approvers []string `json:"approvers,omitempty" yaml:"approvers,omitempty" validate:"required_if=Strategy manual,omitempty,min=1"`
// ApproveIf is an Expression to determines the resolution of the step. If automatic approval is needed for the step,
// use this field.
//
// Accessible parameters:
// $appeal = Appeal object
ApproveIf string `json:"approve_if,omitempty" yaml:"approve_if,omitempty" validate:"required_if=Strategy auto"`
}
type RequirementTrigger struct {
ProviderType string `json:"provider_type" yaml:"provider_type" validate:"required_without_all=ProviderURN ResourceType ResourceURN Role Conditions"`
ProviderURN string `json:"provider_urn" yaml:"provider_urn" validate:"required_without_all=ProviderType ResourceType ResourceURN Role Conditions"`
ResourceType string `json:"resource_type" yaml:"resource_type" validate:"required_without_all=ProviderType ProviderURN ResourceURN Role Conditions"`
ResourceURN string `json:"resource_urn" yaml:"resource_urn" validate:"required_without_all=ProviderType ProviderURN ResourceType Role Conditions"`
Role string `json:"role" yaml:"role" validate:"required_without_all=ProviderType ProviderURN ResourceType ResourceType Conditions"`
Conditions []*Condition `json:"conditions" yaml:"conditions" validate:"required_without_all=ProviderType ProviderURN ResourceType ResourceType Role"`
}
func (r *RequirementTrigger) IsMatch(a *Appeal) (bool, error) {
if r.ProviderType != "" {
if match, err := regexp.MatchString(r.ProviderType, a.Resource.ProviderType); err != nil {
return match, err
} else if !match {
return match, nil
}
}
if r.ProviderURN != "" {
if match, err := regexp.MatchString(r.ProviderURN, a.Resource.ProviderURN); err != nil {
return match, err
} else if !match {
return match, nil
}
}
if r.ResourceType != "" {
if match, err := regexp.MatchString(r.ResourceType, a.Resource.Type); err != nil {
return match, err
} else if !match {
return match, nil
}
}
if r.ResourceURN != "" {
if match, err := regexp.MatchString(r.ResourceURN, a.Resource.URN); err != nil {
return match, err
} else if !match {
return match, nil
}
}
if r.Role != "" {
if match, err := regexp.MatchString(r.Role, a.Role); err != nil {
return match, err
} else if !match {
return match, nil
}
}
if r.Conditions != nil {
for i, c := range r.Conditions {
if match, err := c.IsMatch(a); err != nil {
return match, fmt.Errorf("evaluating conditions[%v]: %v", i, err)
} else if !match {
return match, nil
}
}
}
return true, nil
}
type ResourceIdentifier struct {
ProviderType string `json:"provider_type" yaml:"provider_type" validate:"required_with=ProviderURN Type URN"`
ProviderURN string `json:"provider_urn" yaml:"provider_urn" validate:"required_with=ProviderType Type URN"`
Type string `json:"type" yaml:"type" validate:"required_with=ProviderType ProviderURN URN"`
URN string `json:"urn" yaml:"urn" validate:"required_with=ProviderType ProviderURN Type"`
ID string `json:"id" yaml:"id" validate:"required_without_all=ProviderType ProviderURN Type URN"`
}
type AdditionalAppeal struct {
Resource *ResourceIdentifier `json:"resource" yaml:"resource" validate:"required"`
Role string `json:"role" yaml:"role" validate:"required"`
Options *AppealOptions `json:"options" yaml:"options"`
Policy *PolicyConfig `json:"policy" yaml:"policy"`
}
type Requirement struct {
On *RequirementTrigger `json:"on" yaml:"on" validate:"required"`
Appeals []*AdditionalAppeal `json:"appeals" yaml:"appeals" validate:"required,min=1,dive"`
}
// Policy is the approval policy configuration
type Policy struct {
ID string `json:"id" yaml:"id" validate:"required"`
Version uint `json:"version" yaml:"version" validate:"required"`
Description string `json:"description" yaml:"description"`
Steps []*Step `json:"steps" yaml:"steps" validate:"required,min=1,dive"`
Requirements []*Requirement `json:"requirements,omitempty" yaml:"requirements,omitempty" validate:"omitempty,min=1,dive"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
IAM *IAMConfig `json:"iam,omitempty" yaml:"iam,omitempty" validate:"omitempty,dive"`
CreatedAt time.Time `json:"created_at,omitempty" yaml:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty" yaml:"updated_at,omitempty"`
}
func (p *Policy) HasIAMConfig() bool {
return p.IAM != nil
}
// PolicyService interface
type PolicyService interface {
Create(*Policy) error
Find() ([]*Policy, error)
GetOne(id string, version uint) (*Policy, error)
Update(*Policy) error
}