generated from vshn/go-bootstrap
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deny unprivileged users from setting "k8s.cloudscale.ch/loadbalancer-…
…uuid" (#106) See https://ticket.vshn.net/browse/APUB-669
- Loading branch information
Showing
5 changed files
with
235 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
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
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,76 @@ | ||
package webhooks | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/log" | ||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission" | ||
|
||
"github.com/appuio/appuio-cloud-agent/skipper" | ||
) | ||
|
||
// +kubebuilder:webhook:path=/validate-service-cloudscale-lb,name=validate-service-cloudscale-lb.appuio.io,admissionReviewVersions=v1,sideEffects=none,mutating=false,failurePolicy=Fail,groups="",resources=services,verbs=create;update,versions=v1,matchPolicy=equivalent | ||
|
||
const ( | ||
CloudscaleLoadbalancerUUIDAnnotation = "k8s.cloudscale.ch/loadbalancer-uuid" | ||
) | ||
|
||
// ServiceCloudscaleLBValidator checks if a user is allowed to create a namespace. | ||
// The user or the namespace must have a label with the organization name. | ||
// The organization name is used to count the number of namespaces for the organization. | ||
type ServiceCloudscaleLBValidator struct { | ||
Decoder admission.Decoder | ||
|
||
Skipper skipper.Skipper | ||
} | ||
|
||
// Handle handles the admission requests | ||
func (v *ServiceCloudscaleLBValidator) Handle(ctx context.Context, req admission.Request) admission.Response { | ||
ctx = log.IntoContext(ctx, log.FromContext(ctx). | ||
WithName("webhook.validate-namespace-quota.appuio.io"). | ||
WithValues("id", req.UID, "user", req.UserInfo.Username). | ||
WithValues("namespace", req.Namespace, "name", req.Name, | ||
"group", req.Kind.Group, "version", req.Kind.Version, "kind", req.Kind.Kind)) | ||
|
||
return logAdmissionResponse(ctx, v.handle(ctx, req)) | ||
} | ||
|
||
func (v *ServiceCloudscaleLBValidator) handle(ctx context.Context, req admission.Request) admission.Response { | ||
l := log.FromContext(ctx) | ||
|
||
skip, err := v.Skipper.Skip(ctx, req) | ||
if err != nil { | ||
l.Error(err, "error while checking skipper") | ||
return admission.Errored(http.StatusInternalServerError, err) | ||
} | ||
if skip { | ||
return admission.Allowed("skipped") | ||
} | ||
|
||
var newService corev1.Service | ||
if err := v.Decoder.Decode(req, &newService); err != nil { | ||
l.Error(err, "failed to decode request") | ||
return admission.Errored(http.StatusBadRequest, err) | ||
} | ||
|
||
var oldService corev1.Service | ||
if req.OldObject.Raw != nil { | ||
if err := v.Decoder.DecodeRaw(req.OldObject, &oldService); err != nil { | ||
l.Error(err, "failed to decode old object") | ||
return admission.Errored(http.StatusBadRequest, err) | ||
} | ||
} | ||
|
||
oldAnnotiation := oldService.GetAnnotations()[CloudscaleLoadbalancerUUIDAnnotation] | ||
newAnnotiation := newService.GetAnnotations()[CloudscaleLoadbalancerUUIDAnnotation] | ||
|
||
if oldAnnotiation != newAnnotiation { | ||
l.Info("Loadbalancer UUID changed", "old", oldAnnotiation, "new", newAnnotiation) | ||
return admission.Denied(fmt.Sprintf("%s annotation cannot be changed", CloudscaleLoadbalancerUUIDAnnotation)) | ||
} | ||
|
||
return admission.Allowed("allowed") | ||
} |
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,94 @@ | ||
package webhooks | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/go-logr/logr/testr" | ||
"github.com/stretchr/testify/require" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/log" | ||
|
||
"github.com/appuio/appuio-cloud-agent/skipper" | ||
) | ||
|
||
func Test_ServiceCloudscaleLBValidator_Handle(t *testing.T) { | ||
ctx := log.IntoContext(context.Background(), testr.New(t)) | ||
|
||
tests := map[string]struct { | ||
object client.Object | ||
oldObject client.Object | ||
allowed bool | ||
skip bool | ||
matchMessage string | ||
}{ | ||
"Create allow no annotations": { | ||
object: newService("test", nil, nil), | ||
allowed: true, | ||
}, | ||
"Create allow other annotation": { | ||
object: newService("test", nil, map[string]string{"other": "value"}), | ||
allowed: true, | ||
}, | ||
"Create deny lb annotation": { | ||
object: newService("test", nil, map[string]string{CloudscaleLoadbalancerUUIDAnnotation: "value"}), | ||
allowed: false, | ||
matchMessage: "k8s.cloudscale.ch/loadbalancer-uuid annotation cannot be changed", | ||
}, | ||
|
||
"Create allow skipped": { | ||
object: newService("test", nil, map[string]string{CloudscaleLoadbalancerUUIDAnnotation: "value"}), | ||
allowed: true, | ||
skip: true, | ||
matchMessage: "skipped", | ||
}, | ||
|
||
"Update allow no annotations": { | ||
object: newService("test", nil, nil), | ||
oldObject: newService("test", nil, nil), | ||
allowed: true, | ||
}, | ||
"Update allow other annotation": { | ||
object: newService("test", nil, map[string]string{"other": "value"}), | ||
oldObject: newService("test", nil, map[string]string{"other": "value2"}), | ||
allowed: true, | ||
}, | ||
"Update allow new other annotation": { | ||
object: newService("test", nil, map[string]string{"other": "value"}), | ||
oldObject: newService("test", nil, nil), | ||
allowed: true, | ||
}, | ||
"Update allow delete other annotation": { | ||
object: newService("test", nil, nil), | ||
oldObject: newService("test", nil, map[string]string{"other": "value"}), | ||
allowed: true, | ||
}, | ||
"Update deny add lb annotation": { | ||
object: newService("test", nil, map[string]string{CloudscaleLoadbalancerUUIDAnnotation: "value"}), | ||
oldObject: newService("test", nil, nil), | ||
allowed: false, | ||
matchMessage: "k8s.cloudscale.ch/loadbalancer-uuid annotation cannot be changed", | ||
}, | ||
"Update deny update lb annotation": { | ||
object: newService("test", nil, map[string]string{CloudscaleLoadbalancerUUIDAnnotation: "value2"}), | ||
oldObject: newService("test", nil, map[string]string{CloudscaleLoadbalancerUUIDAnnotation: "value"}), | ||
allowed: false, | ||
matchMessage: "k8s.cloudscale.ch/loadbalancer-uuid annotation cannot be changed", | ||
}, | ||
} | ||
|
||
_, scheme, dec := prepareClient(t) | ||
for name, tC := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
subject := &ServiceCloudscaleLBValidator{ | ||
Decoder: dec, | ||
Skipper: skipper.StaticSkipper{ShouldSkip: tC.skip}, | ||
} | ||
res := subject.Handle(ctx, admissionRequestForObjectWithOldObject(t, tC.object, tC.oldObject, scheme)) | ||
require.Equal(t, tC.allowed, res.Allowed) | ||
if tC.matchMessage != "" { | ||
require.Contains(t, res.Result.Message, tC.matchMessage) | ||
} | ||
}) | ||
} | ||
} |
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