Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject namespace as part of the request. #344

Merged
merged 3 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion chart/gatekeeper-operator/templates/gatekeeper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -486,11 +486,16 @@ spec:
protocol: TCP
resources:
limits:
cpu: 100m
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 999
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /certs
name: cert
Expand Down
52 changes: 52 additions & 0 deletions pkg/target/regolib/autoreject_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package target

test_no_nsselector {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match as {}
with input.review.namespace as "testns"

count(res) == 0
}

test_with_nsselector {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match.namespaceSelector as {}
with input.review.namespace as "testns"

count(res) == 1
}

test_with_empty_ns {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match.namespaceSelector as {}
with input.review.namespace as ""

count(res) == 0
}

test_with_undefined_ns {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match.namespaceSelector as {}
with input.review as {}

count(res) == 0
}

test_with_cached_ns {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match.namespaceSelector as {}
with input.review.namespace as "testns"
with data["{{.DataRoot}}"].cluster["v1"]["Namespace"].testns as {}

count(res) == 0
}

test_with_sideloaded_ns {
res := autoreject_review
with data["{{.ConstraintsRoot}}"].a.b.spec.match.namespaceSelector as {}
with input.review.namespace as "testns"
with input.review._unstable.namespace as {}

count(res) == 0
}

22 changes: 22 additions & 0 deletions pkg/target/regolib/namespace_selector_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package target

test_sideload_match {
matches_nsselector({"namespaceSelector": {"matchLabels": {"hi": "there"}}}) with input.review._unstable.namespace as {"metadata": {"labels": {"hi": "there"}}}
}

test_sideload_no_match {
not matches_nsselector({"namespaceSelector": {"matchLabels": {"hi": "there"}}}) with input.review._unstable.namespace as {"metadata": {"labels": {"bye": "there"}}}
}


test_cache_match {
matches_nsselector({"namespaceSelector": {"matchLabels": {"hi": "there"}}})
with data["{{.DataRoot}}"].cluster["v1"]["Namespace"]["my_namespace"] as {"metadata": {"labels": {"hi": "there"}}}
with input.review.namespace as "my_namespace"
}

test_cache_no_match {
not matches_nsselector({"namespaceSelector": {"matchLabels": {"bye": "there"}}})
with data["{{.DataRoot}}"].cluster["v1"]["Namespace"]["my_namespace"] as {"metadata": {"labels": {"hi": "there"}}}
with input.review.namespace as "my_namespace"
}
14 changes: 13 additions & 1 deletion pkg/target/regolib/src.rego
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ autoreject_review[rejection] {
match := get_default(spec, "match", {})
has_field(match, "namespaceSelector")
not data["{{.DataRoot}}"].cluster["v1"]["Namespace"][input.review.namespace]
not input.review._unstable.namespace
not input.review.namespace == ""
rejection := {
"msg": "Namespace is not cached in OPA.",
"details": {},
Expand Down Expand Up @@ -196,6 +198,15 @@ matches_label_selector(selector, labels) {
# Namespace Selector Logic #
############################

get_ns[out] {
out := input.review._unstable.namespace
}

get_ns[out] {
not input.review._unstable.namespace
out := data["{{.DataRoot}}"].cluster["v1"]["Namespace"][input.review.namespace]
}

matches_namespaces(match) {
not has_field(match, "namespaces")
}
Expand All @@ -212,10 +223,11 @@ matches_nsselector(match) {

matches_nsselector(match) {
has_field(match, "namespaceSelector")
ns := data["{{.DataRoot}}"].cluster["v1"]["Namespace"][input.review.namespace]
get_ns[ns]
matches_namespace_selector(match, ns)
}


# Checks to see if a kubernetes NamespaceSelector matches a namespace with a given set of labels
# A non-existent selector or labels should be represented by an empty object ("{}")
matches_namespace_selector(match, ns) {
Expand Down
19 changes: 19 additions & 0 deletions pkg/target/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/open-policy-agent/frameworks/constraint/pkg/types"
"github.com/pkg/errors"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -38,6 +39,20 @@ func processWipeData() (bool, string, interface{}, error) {
return true, "", nil, nil
}

type SideloadNamespace struct {
AdmissionRequest *admissionv1beta1.AdmissionRequest
Namespace *corev1.Namespace
}

type augmentedReview struct {
*admissionv1beta1.AdmissionRequest
Unstable *unstable `json:"_unstable,omitempty"`
}

type unstable struct {
Namespace *corev1.Namespace `json:"namespace,omitempty"`
}

func processUnstructured(o *unstructured.Unstructured) (bool, string, interface{}, error) {
// Namespace will be "" for cluster objects
gvk := o.GetObjectKind().GroupVersionKind()
Expand Down Expand Up @@ -73,6 +88,10 @@ func (h *K8sValidationTarget) HandleReview(obj interface{}) (bool, interface{},
return true, data, nil
case *admissionv1beta1.AdmissionRequest:
return true, data, nil
case SideloadNamespace:
return true, &augmentedReview{AdmissionRequest: data.AdmissionRequest, Unstable: &unstable{Namespace: data.Namespace}}, nil
case *SideloadNamespace:
return true, &augmentedReview{AdmissionRequest: data.AdmissionRequest, Unstable: &unstable{Namespace: data.Namespace}}, nil
}
return false, nil, nil
}
Expand Down
14 changes: 13 additions & 1 deletion pkg/target/target_template_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ autoreject_review[rejection] {
match := get_default(spec, "match", {})
has_field(match, "namespaceSelector")
not {{.DataRoot}}.cluster["v1"]["Namespace"][input.review.namespace]
not input.review._unstable.namespace
not input.review.namespace == ""
rejection := {
"msg": "Namespace is not cached in OPA.",
"details": {},
Expand Down Expand Up @@ -201,6 +203,15 @@ matches_label_selector(selector, labels) {
# Namespace Selector Logic #
############################

get_ns[out] {
out := input.review._unstable.namespace
}

get_ns[out] {
not input.review._unstable.namespace
out := {{.DataRoot}}.cluster["v1"]["Namespace"][input.review.namespace]
}

matches_namespaces(match) {
not has_field(match, "namespaces")
}
Expand All @@ -217,10 +228,11 @@ matches_nsselector(match) {

matches_nsselector(match) {
has_field(match, "namespaceSelector")
ns := {{.DataRoot}}.cluster["v1"]["Namespace"][input.review.namespace]
get_ns[ns]
matches_namespace_selector(match, ns)
}


# Checks to see if a kubernetes NamespaceSelector matches a namespace with a given set of labels
# A non-existent selector or labels should be represented by an empty object ("{}")
matches_namespace_selector(match, ns) {
Expand Down
13 changes: 12 additions & 1 deletion pkg/webhook/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ import (
"github.com/open-policy-agent/gatekeeper/api"
"github.com/open-policy-agent/gatekeeper/api/v1alpha1"
"github.com/open-policy-agent/gatekeeper/pkg/controller/config"
"github.com/open-policy-agent/gatekeeper/pkg/target"
"github.com/open-policy-agent/gatekeeper/pkg/util"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/manager"
Expand Down Expand Up @@ -273,8 +276,16 @@ func (h *validationHandler) reviewRequest(ctx context.Context, req admission.Req
}
}
}
review := &target.SideloadNamespace{AdmissionRequest: &req.AdmissionRequest}
if req.AdmissionRequest.Namespace != "" {
ns := &corev1.Namespace{}
if err := h.client.Get(ctx, types.NamespacedName{Name: req.AdmissionRequest.Namespace}, ns); err != nil {
return nil, err
}
review.Namespace = ns
}

resp, err := h.opa.Review(ctx, req.AdmissionRequest, opa.Tracing(traceEnabled))
resp, err := h.opa.Review(ctx, review, opa.Tracing(traceEnabled))
if traceEnabled {
log.Info(resp.TraceDump())
}
Expand Down