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

Fix nil pointer issue with webhook #966

Merged
merged 27 commits into from
Jun 22, 2023
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
3 changes: 1 addition & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ jobs:
executor: vm
steps:
- checkout
- *set_environment_variables
- *install_k8s
- *test_k8s

Expand Down Expand Up @@ -161,8 +162,6 @@ workflows:
only: /.*/
- build_and_push:
context: org-global
requires:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was that intentional to remove the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't think it's necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests still run--they just don't block building the docker image

- test
filters:
branches:
ignore: /pull\/[0-9]+/
Expand Down
7 changes: 4 additions & 3 deletions cmd/polaris/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ var webhookCmd = &cobra.Command{
CertDir: certDir,
Port: webhookPort,
WebhookServer: webhook.NewServer(webhook.Options{
CertDir: certDir,
CertDir: certDir,
Port: webhookPort,
CertName: "tls.crt",
KeyName: "tls.key",
}),
Expand All @@ -74,10 +75,10 @@ var webhookCmd = &cobra.Command{
}

if enableValidations {
fwebhook.NewValidateWebhook(mgr, fwebhook.Validator{Config: config, Client: mgr.GetClient()})
fwebhook.NewValidateWebhook(mgr, config)
}
if enableMutations {
fwebhook.NewMutateWebhook(mgr, fwebhook.Mutator{Config: config, Client: mgr.GetClient()})
fwebhook.NewMutateWebhook(mgr, config)
}
logrus.Infof("Polaris webhook server listening on port %d", webhookPort)
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/kube/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ package kube

import (
"bytes"
"fmt"
"context"
"fmt"
"os"
"testing"
"time"
Expand Down
36 changes: 31 additions & 5 deletions pkg/webhook/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/fairwindsops/polaris/pkg/mutation"
"github.com/sirupsen/logrus"
"gomodules.xyz/jsonpatch/v2"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand All @@ -35,41 +36,66 @@ type Mutator struct {
decoder *admission.Decoder
}

var _ admission.Handler = &Mutator{}

// NewMutateWebhook creates a mutating admission webhook for the apiType.
func NewMutateWebhook(mgr manager.Manager, mutator Mutator) {
func NewMutateWebhook(mgr manager.Manager, c config.Configuration) {
path := "/mutate"

mutator := Mutator{
Client: mgr.GetClient(),
decoder: admission.NewDecoder(runtime.NewScheme()),
Config: c,
}
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &mutator})
}

func (m *Mutator) mutate(req admission.Request) ([]jsonpatch.Operation, error) {
results, kubeResources, err := GetValidatedResults(req.AdmissionRequest.Kind.Kind, m.decoder, req, m.Config)
if err != nil {
logrus.Errorf("Error while validating resource: %v", err)
return nil, err
}
if results == nil {
logrus.Infof("Not mutating owned pod")
return nil, nil
}
patches := mutation.GetMutationsFromResult(results)
originalYaml, err := yaml.JSONToYAML(kubeResources.OriginalObjectJSON)
if err != nil {
logrus.Errorf("Failed to convert JSON to YAML: %v", err)
return nil, err
}
mutatedYamlStr, err := mutation.ApplyAllMutations(string(originalYaml), patches)
if err != nil {
logrus.Errorf("Failed to apply mutations: %v", err)
return nil, err
}

mutatedJson, err := yaml.YAMLToJSON([]byte(mutatedYamlStr))
if err != nil {
logrus.Errorf("Failed to convert YAML to JSON: %v", err)
return nil, err
}

ops, err := jsonpatch.CreatePatch(kubeResources.OriginalObjectJSON, mutatedJson)
if err != nil {
logrus.Errorf("Failed to create patch from mutation: %v", err)
return nil, err
}
return jsonpatch.CreatePatch(originalYaml, []byte(mutatedYamlStr))
return ops, nil
}

// Handle for Validator to run validation checks.
func (m *Mutator) Handle(ctx context.Context, req admission.Request) admission.Response {
logrus.Info("Starting request")
logrus.Info("Starting mutation request")
patches, err := m.mutate(req)
if err != nil {
logrus.Errorf("Error while getting mutations: %v", err)
return admission.Errored(403, err)
}
if patches == nil {
logrus.Infof("No patches generated")
return admission.Allowed("Allowed")
}
logrus.Infof("Generated %d patches", len(patches))
return admission.Patched("", patches...)
}
43 changes: 22 additions & 21 deletions pkg/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
Expand All @@ -38,19 +39,14 @@ type Validator struct {
Config config.Configuration
}

// InjectDecoder injects the decoder.
func (v *Validator) InjectDecoder(d *admission.Decoder) error {
logrus.Info("Injecting decoder")
v.decoder = d
return nil
}

var _ admission.Handler = &Validator{}

// NewValidateWebhook creates a validating admission webhook for the apiType.
func NewValidateWebhook(mgr manager.Manager, validator Validator) {
func NewValidateWebhook(mgr manager.Manager, c config.Configuration) {
path := "/validate"

validator := Validator{
Client: mgr.GetClient(),
decoder: admission.NewDecoder(runtime.NewScheme()),
Config: c,
}
mgr.GetWebhookServer().Register(path, &webhook.Admission{Handler: &validator})
}

Expand All @@ -60,35 +56,40 @@ func (v *Validator) handleInternal(req admission.Request) (*validator.Result, ku

// GetValidatedResults returns the validated results.
func GetValidatedResults(kind string, decoder *admission.Decoder, req admission.Request, config config.Configuration) (*validator.Result, kube.GenericResource, error) {
var controller kube.GenericResource
var resource kube.GenericResource
var err error
if kind == "Pod" {
if decoder == nil {
panic("Decoder is nil!")
}
pod := corev1.Pod{}
err := decoder.Decode(req, &pod)
if err != nil {
return nil, controller, err
logrus.Errorf("Failed to decode pod: %v", err)
return nil, resource, err
}
if len(pod.ObjectMeta.OwnerReferences) > 0 {
logrus.Infof("Allowing owned pod %s/%s to pass through webhook", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
return nil, controller, nil
return nil, resource, nil
}
controller, err = kube.NewGenericResourceFromPod(pod, pod)
resource, err = kube.NewGenericResourceFromPod(pod, pod)
} else {
controller, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
resource, err = kube.NewGenericResourceFromBytes(req.Object.Raw)
}
if err != nil {
return nil, controller, err
logrus.Errorf("Failed to create resource: %v", err)
return nil, resource, err
}
controllerResult, err := validator.ApplyAllSchemaChecks(&config, nil, controller)
resourceResult, err := validator.ApplyAllSchemaChecks(&config, nil, resource)
if err != nil {
return nil, controller, err
return nil, resource, err
}
return &controllerResult, controller, nil
return &resourceResult, resource, nil
}

// Handle for Validator to run validation checks.
func (v *Validator) Handle(ctx context.Context, req admission.Request) admission.Response {
logrus.Info("Starting request")
logrus.Info("Starting admission request")
result, _, err := v.handleInternal(req)
if err != nil {
logrus.Errorf("Error validating request: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion test/webhook_cases/passing_test.deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ spec:
containers:
- name: nginx
image: nginx:1.7.9
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
securityContext:
Expand All @@ -26,4 +27,4 @@ spec:
runAsNonRoot: true
capabilities:
drop:
- ALL
- ALL
16 changes: 16 additions & 0 deletions test/webhook_cases/passing_test.pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Pod
metadata:
name: nginx-2
spec:
containers:
- name: nginx
image: nginx:1.7.9
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
8 changes: 5 additions & 3 deletions test/webhook_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function clean_up() {
echo "Uninstalling webhook and webhook config"
kubectl delete validatingwebhookconfigurations polaris-webhook --wait=false || true
kubectl delete validatingwebhookconfigurations polaris-validate-webhook --wait=false || true
kubectl delete validatingwebhookconfigurations polaris-mutate-webhook --wait=false || true
kubectl delete mutatingwebhookconfigurations polaris-mutate-webhook --wait=false || true
kubectl -n polaris delete deploy -l app=polaris --wait=false || true
echo -e "\n\nDone cleaning up\n\n"
}
Expand All @@ -82,11 +82,12 @@ kubectl create ns tests
echo "Installing a bad deployment"
kubectl apply -n scale-test -f ./test/webhook_cases/failing_test.deployment.yaml

echo "Installing the webhook"
echo "Installing the webhook at version $CI_SHA1"
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install polaris fairwinds-stable/polaris --namespace polaris --create-namespace \
--set dashboard.enable=false \
--set webhook.enable=true \
--set webhook.mutate=true \
--set image.tag=$CI_SHA1

echo "Waiting for the webhook to come online"
Expand All @@ -105,6 +106,7 @@ for filename in test/webhook_cases/passing_test.*.yaml; do
if ! kubectl apply -n tests -f $filename; then
ALL_TESTS_PASSED=0
echo -e "${RED}****Test Failed: Polaris prevented a resource with no configuration issues****${NC}"
kubectl logs -n polaris deploy/polaris-webhook
else
echo -e "${GREEN}****Test Passed: Polaris correctly allowed this resource****${NC}"
fi
Expand All @@ -118,7 +120,7 @@ for filename in test/webhook_cases/failing_test.*.yaml; do
if kubectl apply -n tests -f $filename; then
ALL_TESTS_PASSED=0
echo -e "${RED}****Test Failed: Polaris should have prevented this resource due to configuration issues.****${NC}"
kubectl logs -n polaris $(kubectl get po -oname -n polaris | grep webhook)
kubectl logs -n polaris deploy/polaris-webhook
else
echo -e "${GREEN}****Test Passed: Polaris correctly prevented this resource****${NC}"
fi
Expand Down