-
Notifications
You must be signed in to change notification settings - Fork 3
/
validate.go
165 lines (139 loc) · 4.32 KB
/
validate.go
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
// Copyright 2021-2024 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0
package internal
import (
"fmt"
"sync"
"github.com/bufbuild/protovalidate-go"
"go.uber.org/multierr"
"google.golang.org/protobuf/proto"
enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1"
requestv1 "github.com/cerbos/cerbos/api/genpb/cerbos/request/v1"
)
var (
validateFn func(proto.Message) error
validatorOnce sync.Once
)
func Validate[T proto.Message](obj T) error {
validatorOnce.Do(func() {
validator, err := protovalidate.New(
protovalidate.WithMessages(
&enginev1.Principal{},
&enginev1.Resource{},
&policyv1.Policy{},
&requestv1.CheckResourcesRequest{},
&requestv1.PlanResourcesRequest{},
&requestv1.AddOrUpdatePolicyRequest{},
),
)
if err != nil {
validateFn = func(_ proto.Message) error {
return fmt.Errorf("failed to initialize validator: %w", err)
}
} else {
validateFn = func(m proto.Message) error {
return validator.Validate(m)
}
}
})
return validateFn(obj)
}
type Validatable interface {
Err() error
Validate() error
}
func IsValid[T Validatable](obj T) error {
if err := obj.Err(); err != nil {
return err
}
return obj.Validate()
}
func ValidatePolicy(p *policyv1.Policy) error {
if err := Validate(p); err != nil {
return err
}
switch pt := p.PolicyType.(type) {
case *policyv1.Policy_ResourcePolicy:
return validateResourcePolicy(pt.ResourcePolicy)
case *policyv1.Policy_PrincipalPolicy:
return validatePrincipalPolicy(pt.PrincipalPolicy)
case *policyv1.Policy_DerivedRoles:
return validateDerivedRoles(pt.DerivedRoles)
case *policyv1.Policy_ExportVariables:
return validateExportVariables(p)
default:
return fmt.Errorf("unknown policy type %T", pt)
}
}
func validateResourcePolicy(rp *policyv1.ResourcePolicy) (err error) {
ruleNames := make(map[string]int, len(rp.Rules))
for i, rule := range rp.Rules {
ruleName := rule.Name
if ruleName == "" {
ruleName = fmt.Sprintf("#%d", i+1)
}
// check for rule without any roles or derived roles defined
if len(rule.Roles) == 0 && len(rule.DerivedRoles) == 0 {
err = multierr.Append(err, fmt.Errorf("rule %s does not specify any roles or derived roles to match", ruleName))
}
// check for name clashes
if rule.Name == "" {
continue
}
if idx, exists := ruleNames[rule.Name]; exists {
err = multierr.Append(err, fmt.Errorf("rule #%d has the same name as rule #%d: '%s'", i+1, idx, rule.Name))
} else {
ruleNames[rule.Name] = i + 1
}
}
return
}
func validatePrincipalPolicy(rp *policyv1.PrincipalPolicy) (err error) {
resourceNames := make(map[string]int, len(rp.Rules))
for i, resourceRules := range rp.Rules {
if idx, exists := resourceNames[resourceRules.Resource]; exists {
err = multierr.Append(err,
fmt.Errorf("duplicate definition of resource '%s' at #%d (previous definition at #%d)", resourceRules.Resource, i+1, idx))
} else {
resourceNames[resourceRules.Resource] = i + 1
}
ruleNames := make(map[string]int, len(resourceRules.Actions))
for j, actionRule := range resourceRules.Actions {
if actionRule.Name == "" {
continue
}
if idx, exists := ruleNames[actionRule.Name]; exists {
err = multierr.Append(err,
fmt.Errorf("action rule #%d for resource %s has the same name as action rule #%d: '%s'",
j+1, resourceRules.Resource, idx, actionRule.Name))
} else {
ruleNames[actionRule.Name] = j + 1
}
}
}
return
}
func validateDerivedRoles(dr *policyv1.DerivedRoles) (err error) {
roleNames := make(map[string]int, len(dr.Definitions))
for i, rd := range dr.Definitions {
// Check for name clashes
if idx, exists := roleNames[rd.Name]; exists {
err = multierr.Append(err, fmt.Errorf("derived role definition #%d has the same name as definition #%d: '%s'", i+1, idx, rd.Name))
} else {
roleNames[rd.Name] = i + 1
}
// Check for rules without any parent roles
if len(rd.ParentRoles) == 0 {
err = multierr.Append(err, fmt.Errorf("derived role definition '%s' does not specify any parent roles to match", rd.Name))
}
}
return
}
func validateExportVariables(p *policyv1.Policy) error {
//nolint:staticcheck
if len(p.Variables) > 0 {
return fmt.Errorf("export variables policies do not support the deprecated top-level variables field")
}
return nil
}