Skip to content

Commit

Permalink
feat: add metadata via header annotations in rego (#324)
Browse files Browse the repository at this point in the history
* feat: add metadata.labels and metadata.annotations via header annotations in rego

* Add test for annotations
  • Loading branch information
Mattes83 authored Oct 15, 2022
1 parent f8850e1 commit f8859b5
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 2 deletions.
14 changes: 14 additions & 0 deletions docs/constraint_creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,17 @@ missing_labels = missing {
missing := required - provided
}
```
## Setting constraint metadata.annotations and metadata.labels

You can optionally specify annotations and labels for the generated Constraint. This can be useful if you use Argo CD for deployment (see [here](https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/#skip-dry-run-for-new-custom-resources-types)).

```
# METADATA
# title: Required Labels
# description: >-
# This policy allows you to require certain labels are set on a resource.
# custom:
# annotations:
# "argocd.argoproj.io/sync-options": "SkipDryRunOnMissingResource=true"
...
```
2 changes: 2 additions & 0 deletions examples/container-deny-added-caps/constraint.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: ContainerDenyAddedCaps
metadata:
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
name: containerdenyaddedcaps
spec:
match:
Expand Down
2 changes: 2 additions & 0 deletions examples/container-deny-added-caps/src.rego
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# for containers to escalate their privileges. As such, this is not allowed
# outside of Kubernetes controller namespaces.
# custom:
# annotations:
# "argocd.argoproj.io/sync-options": "SkipDryRunOnMissingResource=true"
# matchers:
# kinds:
# - apiGroups:
Expand Down
8 changes: 8 additions & 0 deletions internal/commands/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,14 @@ func getConstraint(violation rego.Rego, logger *log.Entry) (*unstructured.Unstru
var constraint unstructured.Unstructured
constraint.SetGroupVersionKind(gvk)
constraint.SetName(violation.Name())
annotations := violation.Annotations()
if annotations != nil {
constraint.SetAnnotations(annotations)
}
labels := violation.Labels()
if labels != nil {
constraint.SetLabels(labels)
}

if violation.Enforcement() != "deny" {
if err := unstructured.SetNestedField(constraint.Object, violation.Enforcement(), "spec", "enforcementAction"); err != nil {
Expand Down
72 changes: 70 additions & 2 deletions internal/rego/rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,15 @@ const (
annoParameters = "parameters"
annoSkipTemplate = "skipTemplate"
annoSkipConstraint = "skipConstraint"
annoAnnotations = "annotations"
annoLabels = "labels"
)

type MetaData struct {
Annotations map[string]string
Labels map[string]string
}

// Rego represents a parsed rego file.
type Rego struct {
id string
Expand All @@ -53,7 +60,7 @@ type Rego struct {
enforcement string
skipTemplate bool
skipConstraint bool

metaData *MetaData
// Duplicate data from OPA Metadata annotations.
annotations *ast.Annotations
annoTitle string
Expand Down Expand Up @@ -241,9 +248,54 @@ func (r *Rego) parseAnnotations(annotations *ast.Annotations) error {
r.enforcement = e
}

metaAnnotations, ok := annotations.Custom[annoAnnotations]
if ok {
a, ok := metaAnnotations.(map[string]interface{})
if !ok {
return fmt.Errorf("supplied annotations value is not a map[string]interface{}: %T", metaAnnotations)
}
if r.metaData == nil {
r.metaData = &MetaData{}
}
ans, err := switchToMap(a)
if err != nil {
return err
}
r.metaData.Annotations = ans
}

metaLabels, ok := annotations.Custom[annoLabels]
if ok {
l, ok := metaLabels.(map[string]interface{})
if !ok {
return fmt.Errorf("supplied labels value is not a map[string]interface{}: %T", metaLabels)
}
if r.metaData == nil {
r.metaData = &MetaData{}
}
labels, err := switchToMap(l)
if err != nil {
return err
}
r.metaData.Labels = labels
}

return nil
}

func switchToMap(in map[string]interface{}) (map[string]string, error) {
out := map[string]string{}
for k, v := range in {
switch c := v.(type) {
case string:
out[k] = v.(string)
default:
return nil, fmt.Errorf("supplied value is not a string: %v", c)
}
}
return out, nil
}

func (r *Rego) parseAnnotationsMatchers(matchers map[string]any) error {
kindMatchers, ok := matchers["kinds"]
if ok {
Expand Down Expand Up @@ -342,6 +394,22 @@ func (r Rego) Name() string {
return strings.ToLower(r.Kind())
}

// Labels returns the labels found in the header comment of the rego file.
func (r Rego) Labels() map[string]string {
if r.metaData == nil {
return nil
}
return r.metaData.Labels
}

// Annotations returns the annotations found in the header comment of the rego file.
func (r Rego) Annotations() map[string]string {
if r.metaData == nil {
return nil
}
return r.metaData.Annotations
}

// Title returns the title found in the header comment of the rego file.
func (r Rego) Title() string {
if r.annoTitle != "" {
Expand Down Expand Up @@ -441,7 +509,7 @@ func (r Rego) Description() string {
return description
}

// HasMetadataAnnotations checks whenether rego file has
// HasMetadataAnnotations checks whether rego file has
// OPA Metadata Annotations
func (r Rego) HasMetadataAnnotations() bool {
return r.annotations != nil
Expand Down

0 comments on commit f8859b5

Please sign in to comment.