Skip to content

Commit c345f31

Browse files
Keep iterating on policy checking
1 parent 846cbed commit c345f31

File tree

8 files changed

+356
-192
lines changed

8 files changed

+356
-192
lines changed

cmd/kubeapply/subcmd/validate.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,25 @@ func execValidation(ctx context.Context, clusterConfig *config.ClusterConfig) er
102102
numInvalidResources := 0
103103

104104
for _, result := range results {
105-
switch result.Status {
105+
switch result.SchemaStatus {
106106
case validation.StatusValid:
107107
log.Infof("Resource %s in file %s OK", result.PrettyName(), result.Filename)
108108
case validation.StatusSkipped:
109109
log.Debugf("Resource %s in file %s was skipped", result.PrettyName(), result.Filename)
110110
case validation.StatusError:
111111
numInvalidResources++
112-
log.Errorf("File %s could not be validated: %+v", result.Filename, result.Message)
112+
log.Errorf(
113+
"File %s could not be validated: %+v",
114+
result.Filename,
115+
result.SchemaMessage,
116+
)
113117
case validation.StatusInvalid:
114118
numInvalidResources++
115119
log.Errorf(
116120
"Resource %s in file %s is invalid: %s",
117121
result.PrettyName(),
118122
result.Filename,
119-
result.Message,
123+
result.SchemaMessage,
120124
)
121125
case validation.StatusEmpty:
122126
default:

pkg/validation/checker.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package validation
2+
3+
import "context"
4+
5+
// Checker is an interface that checks a resource and then returns a CheckResult.
6+
type Checker interface {
7+
Check(context.Context, Resource) CheckResult
8+
}

pkg/validation/kubeconform.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
6+
"github.com/yannh/kubeconform/pkg/validator"
7+
)
8+
9+
type KubeconformChecker struct {
10+
validatorObj validator.Validator
11+
}
12+
13+
var _ Checker = (*KubeconformChecker)(nil)
14+
15+
func NewKubeconformChecker() (*KubeconformChecker, error) {
16+
validatorObj, err := validator.New(
17+
nil,
18+
validator.Opts{
19+
IgnoreMissingSchemas: true,
20+
Strict: true,
21+
},
22+
)
23+
if err != nil {
24+
return nil, err
25+
}
26+
27+
return &KubeconformChecker{
28+
validatorObj: validatorObj,
29+
}, nil
30+
}
31+
32+
func (k *KubeconformChecker) Check(_ context.Context, resource Resource) CheckResult {
33+
kResult := k.validatorObj.ValidateResource(resource.TokResource())
34+
35+
var message string
36+
if kResult.Err != nil {
37+
message = kResult.Err.Error()
38+
}
39+
40+
return CheckResult{
41+
CheckType: CheckTypeKubeconform,
42+
CheckName: "kubeconform",
43+
Status: kStatusToStatus(kResult.Status),
44+
Message: message,
45+
}
46+
}
47+
48+
func kStatusToStatus(kStatus validator.Status) Status {
49+
switch kStatus {
50+
case validator.Valid:
51+
return StatusValid
52+
case validator.Invalid:
53+
return StatusInvalid
54+
case validator.Error:
55+
return StatusError
56+
case validator.Skipped:
57+
return StatusSkipped
58+
case validator.Empty:
59+
return StatusEmpty
60+
default:
61+
return StatusOther
62+
}
63+
}

pkg/validation/policy.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/ghodss/yaml"
8+
"github.com/open-policy-agent/opa/rego"
9+
)
10+
11+
// Policy wraps a policy module and a prepared query.
12+
type PolicyChecker struct {
13+
Module PolicyModule
14+
Query rego.PreparedEvalQuery
15+
}
16+
17+
var _ Checker = (*PolicyChecker)(nil)
18+
19+
// PolicyModule contains information about a policy.
20+
type PolicyModule struct {
21+
Name string
22+
Contents string
23+
Package string
24+
Result string
25+
}
26+
27+
func NewPolicyChecker(ctx context.Context, module PolicyModule) (*PolicyChecker, error) {
28+
query, err := rego.New(
29+
rego.Query(
30+
fmt.Sprintf("result = %s.%s", module.Package, module.Result),
31+
),
32+
rego.Module(module.Package, module.Contents),
33+
).PrepareForEval(ctx)
34+
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
return &PolicyChecker{
40+
Module: module,
41+
Query: query,
42+
}, nil
43+
}
44+
45+
func (p *PolicyChecker) Check(ctx context.Context, resource Resource) CheckResult {
46+
data := map[string]interface{}{}
47+
result := CheckResult{
48+
CheckType: CheckTypeOPA,
49+
CheckName: p.Module.Name,
50+
}
51+
52+
if err := yaml.Unmarshal(resource.Contents, &data); err != nil {
53+
result.Status = StatusError
54+
result.Message = fmt.Sprintf("Error unmarshalling yaml: %+v", err)
55+
return result
56+
}
57+
58+
results, err := p.Query.Eval(ctx, rego.EvalInput(data))
59+
if err != nil {
60+
result.Status = StatusError
61+
result.Message = fmt.Sprintf("Error evaluating query: %+v", err)
62+
return result
63+
}
64+
65+
if len(results) != 1 {
66+
result.Status = StatusError
67+
result.Message = fmt.Sprintf("Did not get exactly one result: %+v", results)
68+
return result
69+
}
70+
71+
allowed, ok := results[0].Bindings["result"].(bool)
72+
73+
if !ok {
74+
result.Status = StatusError
75+
result.Message = fmt.Sprintf("Did not get exactly one result: %+v", results)
76+
return result
77+
} else if !allowed {
78+
result.Status = StatusInvalid
79+
result.Message = "Policy returned allowed = false"
80+
return result
81+
} else {
82+
result.Status = StatusValid
83+
result.Message = "Policy returned allowed = true"
84+
return result
85+
}
86+
}

pkg/validation/resource.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package validation
2+
3+
import (
4+
"fmt"
5+
6+
kresource "github.com/yannh/kubeconform/pkg/resource"
7+
)
8+
9+
// Resource is a Kubernetes resource from a file that we want to do checks on.
10+
type Resource struct {
11+
Path string
12+
Contents []byte
13+
Name string
14+
Namespace string
15+
Version string
16+
Kind string
17+
18+
index int
19+
}
20+
21+
// MakeResource constructs a resource from a path, contents, and index.
22+
func MakeResource(path string, contents []byte, index int) Resource {
23+
kResource := &kresource.Resource{
24+
Path: path,
25+
Bytes: contents,
26+
}
27+
28+
resource := Resource{
29+
Path: path,
30+
Contents: contents,
31+
index: index,
32+
}
33+
34+
sig, err := kResource.Signature()
35+
if err == nil || sig != nil {
36+
resource.Name = sig.Name
37+
resource.Kind = sig.Kind
38+
resource.Namespace = sig.Namespace
39+
resource.Version = sig.Version
40+
}
41+
42+
return resource
43+
}
44+
45+
// PrettyName returns a pretty, compact name for a resource.
46+
func (r Resource) PrettyName() string {
47+
if r.Kind != "" && r.Version != "" && r.Name != "" && r.Namespace != "" {
48+
// Namespaced resources
49+
return fmt.Sprintf("%s.%s.%s.%s", r.Kind, r.Version, r.Namespace, r.Name)
50+
} else if r.Kind != "" && r.Version != "" && r.Name != "" {
51+
// Non-namespaced resources
52+
return fmt.Sprintf("%s.%s.%s", r.Kind, r.Version, r.Name)
53+
} else {
54+
return r.Name
55+
}
56+
}
57+
58+
func (r Resource) TokResource() kresource.Resource {
59+
return kresource.Resource{
60+
Path: r.Path,
61+
Bytes: r.Contents,
62+
}
63+
}

pkg/validation/result.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package validation
2+
3+
// Status stores the result of validating a single file or resource.
4+
type Status string
5+
6+
const (
7+
StatusValid Status = "valid"
8+
StatusInvalid Status = "invalid"
9+
StatusError Status = "error"
10+
StatusSkipped Status = "skipped"
11+
StatusEmpty Status = "empty"
12+
StatusOther Status = "other"
13+
)
14+
15+
type CheckType string
16+
17+
const (
18+
CheckTypeKubeconform CheckType = "kubeconform"
19+
CheckTypeOPA CheckType = "opa"
20+
)
21+
22+
// Result stores the results of validating a single resource in a single file.
23+
type Result struct {
24+
Resource Resource
25+
CheckResults []CheckResult
26+
}
27+
28+
type CheckResult struct {
29+
CheckType CheckType
30+
CheckName string
31+
Status Status
32+
Message string
33+
}

0 commit comments

Comments
 (0)