github.com/argoproj/argo-events@v1.9.1/webhook/webhook_test.go (about)

     1  package webhook
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"net"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	admissionv1 "k8s.io/api/admission/v1"
    12  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    13  	appsv1 "k8s.io/api/apps/v1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/runtime"
    16  	"k8s.io/apimachinery/pkg/runtime/schema"
    17  	fakeClient "k8s.io/client-go/kubernetes/fake"
    18  	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
    19  
    20  	"github.com/argoproj/argo-events/common/logging"
    21  	eventbusv1alphal1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
    22  	eventsourcev1alphal1 "github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    23  	sensorv1alphal1 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    24  )
    25  
    26  func fakeOptions() Options {
    27  	return Options{
    28  		Namespace:       "test-ns",
    29  		DeploymentName:  "events-webhook",
    30  		ClusterRoleName: "argo-events-webhook",
    31  		ServiceName:     "webhook",
    32  		Port:            8443,
    33  		SecretName:      "webhook-certs",
    34  		WebhookName:     "webhook.argo-events.argoproj.io",
    35  	}
    36  }
    37  
    38  func fakeValidatingWebhookConfig(opts Options) *admissionregistrationv1.ValidatingWebhookConfiguration {
    39  	sideEffects := admissionregistrationv1.SideEffectClassNone
    40  	failurePolicy := admissionregistrationv1.Ignore
    41  	return &admissionregistrationv1.ValidatingWebhookConfiguration{
    42  		ObjectMeta: metav1.ObjectMeta{
    43  			Name: opts.WebhookName,
    44  		},
    45  		Webhooks: []admissionregistrationv1.ValidatingWebhook{
    46  			{
    47  				Name:          opts.WebhookName,
    48  				Rules:         []admissionregistrationv1.RuleWithOperations{{}},
    49  				SideEffects:   &sideEffects,
    50  				FailurePolicy: &failurePolicy,
    51  				ClientConfig:  admissionregistrationv1.WebhookClientConfig{},
    52  			},
    53  		},
    54  	}
    55  }
    56  
    57  func contextWithLogger(t *testing.T) context.Context {
    58  	t.Helper()
    59  	return logging.WithLogger(context.Background(), logging.NewArgoEventsLogger())
    60  }
    61  
    62  func contextWithLoggerAndCancel(t *testing.T) context.Context {
    63  	t.Helper()
    64  	return logging.WithLogger(signals.SetupSignalHandler(), logging.NewArgoEventsLogger())
    65  }
    66  
    67  func fakeAdmissionController(t *testing.T, options Options) *AdmissionController {
    68  	t.Helper()
    69  	ac := &AdmissionController{
    70  		Client:  fakeClient.NewSimpleClientset(),
    71  		Options: options,
    72  		Handlers: map[schema.GroupVersionKind]runtime.Object{
    73  			eventbusv1alphal1.SchemaGroupVersionKind:    &eventbusv1alphal1.EventBus{},
    74  			eventsourcev1alphal1.SchemaGroupVersionKind: &eventsourcev1alphal1.EventSource{},
    75  			sensorv1alphal1.SchemaGroupVersionKind:      &sensorv1alphal1.Sensor{},
    76  		},
    77  		Logger: logging.NewArgoEventsLogger(),
    78  	}
    79  	return ac
    80  }
    81  
    82  func TestConnectAllowed(t *testing.T) {
    83  	ac := fakeAdmissionController(t, fakeOptions())
    84  	t.Run("test CONNECT allowed", func(t *testing.T) {
    85  		req := &admissionv1.AdmissionRequest{
    86  			Operation: admissionv1.Connect,
    87  		}
    88  		resp := ac.admit(contextWithLogger(t), req)
    89  		assert.True(t, resp.Allowed)
    90  	})
    91  }
    92  
    93  func TestDeleteAllowed(t *testing.T) {
    94  	ac := fakeAdmissionController(t, fakeOptions())
    95  	t.Run("test DELETE allowed", func(t *testing.T) {
    96  		req := &admissionv1.AdmissionRequest{
    97  			Operation: admissionv1.Delete,
    98  		}
    99  		resp := ac.admit(contextWithLogger(t), req)
   100  		assert.True(t, resp.Allowed)
   101  	})
   102  }
   103  
   104  func TestUnknownKindFails(t *testing.T) {
   105  	ac := fakeAdmissionController(t, fakeOptions())
   106  
   107  	t.Run("test unknown types", func(t *testing.T) {
   108  		req := &admissionv1.AdmissionRequest{
   109  			Operation: admissionv1.Create,
   110  			Kind: metav1.GroupVersionKind{
   111  				Group:   "test.test.test",
   112  				Version: "v1alpha1",
   113  				Kind:    "Unknown",
   114  			},
   115  		}
   116  		resp := ac.admit(contextWithLogger(t), req)
   117  		assert.True(t, !resp.Allowed)
   118  	})
   119  }
   120  
   121  func TestDefaultClientAuth(t *testing.T) {
   122  	opts := fakeOptions()
   123  	assert.Equal(t, opts.ClientAuth, tls.NoClientCert)
   124  }
   125  
   126  func createDeployment(ac *AdmissionController) {
   127  	opts := fakeOptions()
   128  	deployment := &appsv1.Deployment{
   129  		ObjectMeta: metav1.ObjectMeta{
   130  			Name:      opts.DeploymentName,
   131  			Namespace: opts.Namespace,
   132  		},
   133  	}
   134  	_, _ = ac.Client.AppsV1().Deployments(opts.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
   135  }
   136  
   137  func createWebhook(ac *AdmissionController, wh *admissionregistrationv1.ValidatingWebhookConfiguration) {
   138  	client := ac.Client.AdmissionregistrationV1().ValidatingWebhookConfigurations()
   139  	_, err := client.Create(context.TODO(), wh, metav1.CreateOptions{})
   140  	if err != nil {
   141  		panic(fmt.Errorf("failed to create test webhook, %w", err))
   142  	}
   143  }
   144  
   145  func TestRun(t *testing.T) {
   146  	opts := fakeOptions()
   147  	ac := fakeAdmissionController(t, opts)
   148  	createDeployment(ac)
   149  	webhook := fakeValidatingWebhookConfig(opts)
   150  	createWebhook(ac, webhook)
   151  
   152  	ctx := contextWithLoggerAndCancel(t)
   153  	go func() {
   154  		_ = ac.Run(ctx)
   155  	}()
   156  	_, err := net.Dial("tcp", fmt.Sprintf(":%d", opts.Port))
   157  	assert.NotNil(t, err)
   158  }
   159  
   160  func TestConfigureCertWithExistingSecret(t *testing.T) {
   161  	t.Run("test configure cert with existing secret", func(t *testing.T) {
   162  		opts := fakeOptions()
   163  		ac := fakeAdmissionController(t, opts)
   164  		createDeployment(ac)
   165  		ctx := contextWithLogger(t)
   166  		newSecret, err := ac.generateSecret(ctx)
   167  		assert.Nil(t, err)
   168  		_, err = ac.Client.CoreV1().Secrets(opts.Namespace).Create(context.TODO(), newSecret, metav1.CreateOptions{})
   169  		assert.Nil(t, err)
   170  
   171  		tlsConfig, caCert, err := ac.configureCerts(ctx, tls.NoClientCert)
   172  		assert.Nil(t, err)
   173  		assert.NotNil(t, tlsConfig)
   174  
   175  		expectedCert, err := tls.X509KeyPair(newSecret.Data[secretServerCert], newSecret.Data[secretServerKey])
   176  		assert.Nil(t, err)
   177  		assert.True(t, len(tlsConfig.Certificates) >= 1)
   178  
   179  		assert.Equal(t, expectedCert.Certificate, tlsConfig.Certificates[0].Certificate)
   180  		assert.Equal(t, newSecret.Data[secretCACert], caCert)
   181  	})
   182  }