-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Brendan Ryan
committed
Jul 4, 2019
0 parents
commit 22830ac
Showing
11 changed files
with
662 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ccheck |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
default: build | ||
|
||
vet: | ||
go vet . | ||
|
||
build: | ||
go build . | ||
|
||
test: | ||
go test ./... | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# `ccheck` | ||
--- | ||
|
||
`ccheck` is a command line application for writing tests against configuration files and data using the [`rego` query language](https://www.openpolicyagent.org/docs/latest). It was primarily written for checking Kubernetes config files, but is generic enough to be used for any structured data type (`json`, `toml` etc..) | ||
|
||
## Usage | ||
|
||
The `ccheck` binary uses two special `rego` declarations, `deny` and `warn` during its evaluation process. If a resource matches a `deny` rule, a failure will be issued, otherwise a `warning` will be logged to the command line. An example of a valid, well-formed `ccheck` config is as follows: | ||
|
||
**Example `*.rego file`** | ||
|
||
```rego | ||
package main | ||
is_hpa { | ||
input.kind = "HorizontalPodAutoscaler" | ||
} | ||
# checks that we do not include any horizontal pod autoscalers | ||
deny[msg] { | ||
not is_hpa | ||
msg = sprintf("%s must not include any Horizontal Pod AutoScalers", [input.metadata.name]) | ||
} | ||
# checks that apps do not live in the default namespace | ||
warn[msg] { | ||
not input.metadata.namespace = "default" | ||
msg = sprintf("%s should not be configured to live in the default namespace", [input.metadata.name]) | ||
``` | ||
|
||
`ccheck` can then be invoked using this policy via: | ||
|
||
```bash | ||
ccheck -p <policy directory> <files to check....> | ||
``` | ||
|
||
For example using the following file: | ||
|
||
**Example Kubernetes `.yaml` file** | ||
|
||
```yaml | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: nginx-deployment | ||
labels: | ||
app: nginx | ||
spec: | ||
replicas: 3 | ||
selector: | ||
matchLabels: | ||
app: nginx | ||
template: | ||
metadata: | ||
labels: | ||
app: nginx | ||
spec: | ||
containers: | ||
- name: nginx | ||
image: nginx:1.7.9 | ||
ports: | ||
- containerPort: 80 | ||
|
||
--- | ||
|
||
apiVersion: autoscaling/v1 | ||
kind: HorizontalPodAutoscaler | ||
metadata: | ||
name: nginx | ||
namespace: default | ||
spec: | ||
scaleTargetRef: | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
name: nginx | ||
minReplicas: 1 | ||
maxReplicas: 10 | ||
targetCPUUtilizationPercentage: 50 | ||
``` | ||
Will produce the following output: | ||
```bash | ||
Warning: /Users/brendanjryan/projects/ccheck/example/test.yaml - nginx-deployment should not be configured to live in the default namespace | ||
Failure: /Users/brendanjryan/projects/ccheck/example/test.yaml - nginx-deployment must not include any Horizontal Pod AutoScalers | ||
brendanjryan@Brendans-MacBook-Pro:~/projects/ccheck| | ||
``` | ||
|
||
|
||
**Full Example:** | ||
|
||
If you would like to see `ccheck` in action - this project bundles this example in its source as well. Just `clone` this project and run: | ||
|
||
|
||
```bash | ||
./ccheck -p example/policies example/test.yaml | ||
Warning: /Users/brendanjryan/projects/ccheck/example/test.yaml - nginx-deployment should not be configured to live in the default namespace | ||
Failure: /Users/brendanjryan/projects/ccheck/example/test.yaml - nginx-deployment must not include any Horizontal Pod AutoScalers | ||
brendanjryan@Brendans-MacBook-Pro:~/projects/ccheck| | ||
``` | ||
|
||
## FAQ | ||
|
||
- Why use `rego` instead of another declarative language like `hcl`? | ||
|
||
Although `rego` is a very new and domain specific language, it's simple grammar and extensibility were the main motivators in using it instead of a more popular declarative language or framework. As an added bonus, you can re-use your policies declared in `rego` right out of the box in [kubernetes admission controllers](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) powered by [Open Policy Agent](https://www.openpolicyagent.org/) | ||
|
||
## Additional References | ||
|
||
- [Rego language spec](https://www.openpolicyagent.org/docs/latest) | ||
- [Open Policy Agent Project](https://www.openpolicyagent.org/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package main | ||
|
||
is_hpa { | ||
input.kind = "HorizontalPodAutoscaler" | ||
} | ||
|
||
# checks that we do not include any horizontal pod autoscalers | ||
deny[msg] { | ||
not is_hpa | ||
msg = sprintf("%s must not include any Horizontal Pod AutoScalers", [input.metadata.name]) | ||
} | ||
|
||
# checks that apps do not live in the default namespace | ||
warn[msg] { | ||
not input.metadata.namespace = "default" | ||
msg = sprintf("%s should not be configured to live in the default namespace", [input.metadata.name]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: nginx-deployment | ||
labels: | ||
app: nginx | ||
spec: | ||
replicas: 3 | ||
selector: | ||
matchLabels: | ||
app: nginx | ||
template: | ||
metadata: | ||
labels: | ||
app: nginx | ||
spec: | ||
containers: | ||
- name: nginx | ||
image: nginx:1.7.9 | ||
ports: | ||
- containerPort: 80 | ||
|
||
--- | ||
|
||
apiVersion: autoscaling/v1 | ||
kind: HorizontalPodAutoscaler | ||
metadata: | ||
name: nginx | ||
namespace: default | ||
spec: | ||
scaleTargetRef: | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
name: nginx | ||
minReplicas: 1 | ||
maxReplicas: 10 | ||
targetCPUUtilizationPercentage: 50 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
module github.com/brendanjryan/ccheck | ||
|
||
go 1.12 | ||
|
||
require ( | ||
github.com/OneOfOne/xxhash v1.2.5 // indirect | ||
github.com/fatih/color v1.7.0 | ||
github.com/ghodss/yaml v1.0.0 | ||
github.com/gobwas/glob v0.2.3 // indirect | ||
github.com/hashicorp/go-multierror v1.0.0 | ||
github.com/kr/pretty v0.1.0 // indirect | ||
github.com/mattn/go-colorable v0.1.2 // indirect | ||
github.com/open-policy-agent/opa v0.12.0 | ||
github.com/pkg/errors v0.8.1 // indirect | ||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect | ||
github.com/uber-go/multierr v1.1.0 | ||
github.com/urfave/cli v1.20.0 | ||
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect | ||
go.uber.org/atomic v1.4.0 // indirect | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | ||
gopkg.in/yaml.v2 v2.2.2 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= | ||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= | ||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= | ||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | ||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= | ||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= | ||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= | ||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= | ||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= | ||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= | ||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= | ||
github.com/open-policy-agent/opa v0.12.0 h1:ocbBbALdMH/JXyC9w8M1P1D82gs57e0xZ4QoRRFREJQ= | ||
github.com/open-policy-agent/opa v0.12.0/go.mod h1:rlfeSeHuZmMEpmrcGla42AjkOUjP4rGIpS96H12un3o= | ||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= | ||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= | ||
github.com/uber-go/multierr v1.1.0 h1:0yjbRFohBIYjhub5/9AKZtV0P7079XboeGwXz0ccR9w= | ||
github.com/uber-go/multierr v1.1.0/go.mod h1:ezSsblYU20Gqx98LnaYrIdu6KxYN1ctSsX09RsSjk5Y= | ||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= | ||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | ||
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= | ||
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= | ||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= | ||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"os" | ||
|
||
"github.com/brendanjryan/ccheck/pkg" | ||
"github.com/fatih/color" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
func main() { | ||
app := cli.NewApp() | ||
|
||
app.Name = "ccheck" | ||
app.Usage = "ccheck <files>" | ||
app.Author = "Brendan Ryan" | ||
app.Description = "A command line utility for validating structured config files" | ||
|
||
as := args{} | ||
|
||
app.Flags = []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "p", | ||
Value: "policies", | ||
Usage: "directory which policy definitions live in", | ||
Destination: &as.policyDir, | ||
}, | ||
cli.StringFlag{ | ||
Name: "n", | ||
Value: "main", | ||
Usage: "namespace of rules", | ||
Destination: &as.namespace, | ||
}, | ||
cli.BoolFlag{ | ||
Name: "s", | ||
Usage: "whether or not strict mode is enabled", | ||
Destination: &as.strict, | ||
}, | ||
} | ||
|
||
app.Action = func(c *cli.Context) error { | ||
as.configs = c.Args() | ||
err := confCheck(as) | ||
if err != nil { | ||
log.Fatal(cli.NewExitError("error: "+err.Error(), 1)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
err := app.Run(os.Args) | ||
if err != nil { | ||
log.Fatal("error creating CLI application: ", err) | ||
} | ||
|
||
return | ||
} | ||
|
||
func confCheck(as args) error { | ||
ctx := context.Background() | ||
|
||
cc := pkg.NewConfChecker(as.namespace, as.policyDir, as.configs) | ||
|
||
cr, err := cc.Run(ctx) | ||
if err != nil { | ||
log.Println("error running checker: ", err) | ||
return err | ||
} | ||
|
||
p := printer{} | ||
for f, res := range cr { | ||
if len(res.Warnings) == 0 && len(res.Failures) == 0 { | ||
p.ok(f) | ||
} | ||
|
||
for _, w := range res.Warnings { | ||
if as.strict { | ||
p.err(f, w) | ||
continue | ||
} | ||
|
||
p.warning(f, w) | ||
} | ||
|
||
for _, fa := range res.Failures { | ||
// trap an error just so we exit with the right code | ||
err = fa | ||
p.err(f, fa) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// args represents all command line arguments supported by this script | ||
type args struct { | ||
// the directory which policy files live in | ||
policyDir string | ||
|
||
// the namespace rules live in: | ||
// https://www.openpolicyagent.org/docs/latest/how-do-i-write-policies#packages | ||
namespace string | ||
|
||
// whether or not strict mode is enabled | ||
strict bool | ||
|
||
// a list of config files we will check | ||
configs []string | ||
} | ||
|
||
// printer controlls printing the results of this script in a formatted manner. | ||
type printer struct{} | ||
|
||
func (p printer) err(file string, err error) { | ||
color.Red("Failure: %s - %s", file, err) | ||
} | ||
|
||
func (p printer) warning(file string, err error) { | ||
color.Yellow("Warning: %s - %s", file, err) | ||
} | ||
|
||
func (p printer) ok(file string) { | ||
color.Green("Passed: %s", file) | ||
} |
Oops, something went wrong.