diff --git a/internal/manifests/keys_secret.go b/internal/manifests/secret.go similarity index 80% rename from internal/manifests/keys_secret.go rename to internal/manifests/secret.go index 5ce7985..56f5cd0 100644 --- a/internal/manifests/keys_secret.go +++ b/internal/manifests/secret.go @@ -19,6 +19,11 @@ type SecretBuilder struct { secretType corev1.SecretType } +type TlsCredential interface { + Certificate() []byte + PrivateKey() []byte +} + func NewSecretBuilder() *SecretBuilder { return &SecretBuilder{ secretType: corev1.SecretTypeOpaque, @@ -34,6 +39,15 @@ func (b *SecretBuilder) WithSSHKey(keyPair selfhosted.KeyPair) *SecretBuilder { return b } +func (b *SecretBuilder) WithCertificate(t TlsCredential) *SecretBuilder { + b.data = map[string][]byte{ + "tls.crt": t.Certificate(), + "tls.key": t.PrivateKey(), + } + b.secretType = corev1.SecretTypeTLS + return b +} + func (b *SecretBuilder) Build(namespacedName types.NamespacedName) (*corev1.Secret, error) { secret := &corev1.Secret{ ObjectMeta: v1.ObjectMeta{ diff --git a/internal/selfhosted/certificate.go b/internal/selfhosted/certificate.go index 7ea5bed..54170da 100644 --- a/internal/selfhosted/certificate.go +++ b/internal/selfhosted/certificate.go @@ -1,11 +1 @@ package selfhosted - -import ( - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type WebHook interface { - Resources() []client.Object - Create() - Delete() -} diff --git a/internal/selfhosted/webhook.go b/internal/selfhosted/webhook.go new file mode 100644 index 0000000..959ef16 --- /dev/null +++ b/internal/selfhosted/webhook.go @@ -0,0 +1,9 @@ +package selfhosted + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Webhook interface { + Resources() []client.Object +} diff --git a/internal/selfhosted/webhook/auth.yaml b/internal/selfhosted/webhook/auth.yaml deleted file mode 100644 index b4ceb4b..0000000 --- a/internal/selfhosted/webhook/auth.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: pod-identity-webhook - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: pod-identity-webhook -rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - create -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - - update - - patch - resourceNames: - - "pod-identity-webhook" -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - get - - watch - - list -- apiGroups: - - certificates.k8s.io - resources: - - certificatesigningrequests - verbs: - - create - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: pod-identity-webhook -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: pod-identity-webhook -subjects: -- kind: ServiceAccount - name: pod-identity-webhook - namespace: kube-system \ No newline at end of file diff --git a/internal/selfhosted/webhook/base_manifests.go b/internal/selfhosted/webhook/base_manifests.go index 62bba96..9044a34 100644 --- a/internal/selfhosted/webhook/base_manifests.go +++ b/internal/selfhosted/webhook/base_manifests.go @@ -6,188 +6,224 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" ) -var mutatingWebhookConfiguration = v1beta1.MutatingWebhookConfiguration{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "admissionregistration.k8s.io/v1beta1", - Kind: "MutatingWebhookConfiguration", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-identity-webhook", - Namespace: "kube-system", - }, - Webhooks: []v1beta1.MutatingWebhook{ - { - Name: "pod-identity-webhook.amazonaws.com", - ClientConfig: v1beta1.WebhookClientConfig{ - Service: &v1beta1.ServiceReference{ - Name: "pod-identity-webhook", - Namespace: "kube-system", - Path: "/mutate", - }, - CABundle: []byte("${CA_BUNDLE}"), - }, - Rules: []v1beta1.RuleWithOperations{ - { - Operations: []v1beta1.OperationType{"CREATE"}, - Rule: v1beta1.Rule{ - APIGroups: []string{""}, - APIVersions: []string{"v1"}, - Resources: []string{"pods"}, - }, - }, - }, - FailurePolicy: (*v1beta1.FailurePolicyType)(nil), - }, - }, +type baseManifestFactory struct { + deploymentMeta types.NamespacedName + serviceMeta types.NamespacedName + serviceAccountMeta types.NamespacedName + mutatingWebhookConfigurationMeta types.NamespacedName + podLabel map[string]string } -var deployment = appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ +func serviceNamespacedName() types.NamespacedName { + return types.NamespacedName{ Name: "pod-identity-webhook", - Namespace: "kube-system", - }, - Spec: appsv1.DeploymentSpec{ - Replicas: 1, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "pod-identity-webhook"}, + Namespace: WEBHOOK_NAMESPACE, + } +} + +const WEBHOOK_NAMESPACE = "kube-system" + +func newBaseManifestFactory() *baseManifestFactory { + return &baseManifestFactory{ + deploymentMeta: types.NamespacedName{ + Name: "pod-identity-webhook", + Namespace: WEBHOOK_NAMESPACE, }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"app": "pod-identity-webhook"}, - }, - Spec: corev1.PodSpec{ - ServiceAccountName: "pod-identity-webhook", - Containers: []corev1.Container{ + serviceMeta: serviceNamespacedName(), + serviceAccountMeta: types.NamespacedName{ + Name: "pod-identity-webhook", + Namespace: WEBHOOK_NAMESPACE, + }, + mutatingWebhookConfigurationMeta: types.NamespacedName{ + Name: "pod-identity-webhook", + Namespace: WEBHOOK_NAMESPACE, + }, + podLabel: map[string]string{"app": "pod-identity-webhook"}, + } +} + +func (b *baseManifestFactory) mutatingWebhookConfiguration() *v1beta1.MutatingWebhookConfiguration { + path := "/mutate" + failurePolicy := v1beta1.Ignore + return &v1beta1.MutatingWebhookConfiguration{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: "MutatingWebhookConfiguration", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.mutatingWebhookConfigurationMeta.Name, + Namespace: b.mutatingWebhookConfigurationMeta.Namespace, + }, + Webhooks: []v1beta1.MutatingWebhook{ + { + Name: "pod-identity-webhook.amazonaws.com", + ClientConfig: v1beta1.WebhookClientConfig{ + Service: &v1beta1.ServiceReference{ + Name: b.serviceMeta.Name, + Namespace: b.serviceMeta.Namespace, + Path: &path, + }, + }, + Rules: []v1beta1.RuleWithOperations{ { - Name: "pod-identity-webhook", - Image: "quay.io/amis/pod-identity-webhook:v0.0.1", - ImagePullPolicy: corev1.PullAlways, - Command: []string{"/webhook", "--in-cluster", "--namespace=kube-system", "--service-name=pod-identity-webhook", "--tls-secret=pod-identity-webhook", "--annotation-prefix=eks.amazonaws.com", "--token-audience=sts.amazonaws.com", "--logtostderr"}, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "webhook-certs", - MountPath: "/var/run/app/certs", - ReadOnly: false, + Operations: []v1beta1.OperationType{"CREATE"}, + Rule: v1beta1.Rule{ + APIGroups: []string{""}, + APIVersions: []string{"v1"}, + Resources: []string{"pods"}, + }, + }, + }, + FailurePolicy: &failurePolicy, + }, + }, + } +} + +func (b *baseManifestFactory) deployment() *appsv1.Deployment { + replicas := int32(1) + return &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "Deployment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.deploymentMeta.Name, + Namespace: b.deploymentMeta.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: b.podLabel, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: b.podLabel, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: b.serviceAccountMeta.Name, + Containers: []corev1.Container{ + { + Name: "pod-identity-webhook", + Image: "quay.io/amis/pod-identity-webhook:v0.0.1", + ImagePullPolicy: corev1.PullAlways, + // Command: []string{}, // Command must be patched + VolumeMounts: []corev1.VolumeMount{ + { + Name: "webhook-certs", + MountPath: "/var/run/app/certs", + ReadOnly: false, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "webhook-certs", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }, }, }, - Volumes: []corev1.Volume{ - { - Name: "webhook-certs", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, + }, + }, + } +} + +func (b *baseManifestFactory) serviceAccount() *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ServiceAccount", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.serviceAccountMeta.Name, + Namespace: b.serviceAccountMeta.Namespace, + }, + } +} + +func (b *baseManifestFactory) clusterRole() *rbacv1.ClusterRole { + return &rbacv1.ClusterRole{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacv1.SchemeGroupVersion.String(), + Kind: "ClusterRole", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-identity-webhook", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + Verbs: []string{"create", "get", "update", "patch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"serviceaccounts"}, + Verbs: []string{"get", "watch", "list"}, + }, + { + APIGroups: []string{"certificates.k8s.io"}, + Resources: []string{"certificatesigningrequests"}, + Verbs: []string{"create", "get", "list", "watch"}, + }, + }, + } +} + +func (b *baseManifestFactory) clusterRoleBinding() *rbacv1.ClusterRoleBinding { + return &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacv1.SchemeGroupVersion.String(), + Kind: "ClusterRoleBinding", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-identity-webhook", + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.SchemeGroupVersion.Group, + Kind: "ClusterRole", + Name: "pod-identity-webhook", + }, + Subjects: []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: b.serviceAccountMeta.Name, + Namespace: b.serviceAccountMeta.Namespace, + }, + }, + } +} + +func (b *baseManifestFactory) service() *corev1.Service { + return &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: b.serviceMeta.Name, + Namespace: b.serviceMeta.Namespace, + Annotations: map[string]string{ + "prometheus.io/port": "443", + "prometheus.io/scheme": "https", + "prometheus.io/scrape": "true", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Port: 443, + TargetPort: intstr.FromInt(443), }, }, + Selector: b.podLabel, }, - }, -} - -var serviceAccount = corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-identity-webhook", - Namespace: "kube-system", - }, -} - -type ClusterRole struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Rules []rbacv1.PolicyRule `json:"rules,omitempty"` -} - -var clusterRole = ClusterRole{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRole", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-identity-webhook", - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{""}, - Resources: []string{"secrets"}, - Verbs: []string{"create", "get", "update", "patch"}, - }, - { - APIGroups: []string{""}, - Resources: []string{"serviceaccounts"}, - Verbs: []string{"get", "watch", "list"}, - }, - { - APIGroups: []string{"certificates.k8s.io"}, - Resources: []string{"certificatesigningrequests"}, - Verbs: []string{"create", "get", "list", "watch"}, - }, - }, -} - -type ClusterRoleBinding struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - RoleRef rbacv1.RoleRef `json:"roleRef"` - Subjects []rbacv1.Subject `json:"subjects"` -} - -var clusterRoleBinding = ClusterRoleBinding{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "rbac.authorization.k8s.io/v1", - Kind: "ClusterRoleBinding", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-identity-webhook", - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: "pod-identity-webhook", - }, - Subjects: []rbacv1.Subject{ - { - Kind: "ServiceAccount", - Name: "pod-identity-webhook", - Namespace: "kube-system", - }, - }, -} - -var service = corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-identity-webhook", - Namespace: "kube-system", - Annotations: map[string]string{ - "prometheus.io/port": "443", - "prometheus.io/scheme": "https", - "prometheus.io/scrape": "true", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Port: 443, - TargetPort: intstr.FromInt(443), - }, - }, - Selector: map[string]string{ - "app": "pod-identity-webhook", - }, - }, + } } diff --git a/internal/selfhosted/webhook/base_manifests_test.go b/internal/selfhosted/webhook/base_manifests_test.go new file mode 100644 index 0000000..6d93e12 --- /dev/null +++ b/internal/selfhosted/webhook/base_manifests_test.go @@ -0,0 +1,108 @@ +package webhook + +import ( + "os" + "testing" + + "github.com/goccy/go-yaml" + "github.com/stretchr/testify/assert" + "k8s.io/api/admissionregistration/v1beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestBaseManifests(t *testing.T) { + b := newBaseManifestFactory() + tests := []struct { + name string + runFunc func() client.Object + expected string + expectedFunc func() client.Object + }{ + { + name: "mutatingwebhookconfiguration", + runFunc: func() client.Object { + return b.mutatingWebhookConfiguration() + }, + expected: "testdata/mutatingwebhook.yaml", + expectedFunc: testMutatingWebhookConfiguration, + }, + { + name: "service", + runFunc: func() client.Object { + return b.service() + }, + expected: "testdata/service.yaml", + expectedFunc: testService, + }, + { + name: "deployment", + runFunc: func() client.Object { + return b.deployment() + }, + expected: "testdata/deployment.yaml", + expectedFunc: testDeployment, + }, + { + name: "serviceaccount", + runFunc: func() client.Object { + return b.serviceAccount() + }, + expected: "testdata/serviceaccount.yaml", + expectedFunc: testServiceAccount, + }, + { + name: "clusterrole", + runFunc: func() client.Object { + return b.clusterRole() + }, + expected: "testdata/clusterrole.yaml", + expectedFunc: testClusterRole, + }, + { + name: "clusterrolebinding", + runFunc: func() client.Object { + return b.clusterRoleBinding() + }, + expected: "testdata/clusterrolebinding.yaml", + expectedFunc: testClusterRoleBinding, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.runFunc() + data, err := os.ReadFile(tt.expected) + assert.NoError(t, err) + expected := tt.expectedFunc() + err = yaml.UnmarshalWithOptions(data, expected, yaml.UseJSONUnmarshaler()) + assert.NoError(t, err) + assert.Equal(t, expected, actual) + }) + } +} + +func testMutatingWebhookConfiguration() client.Object { + return &v1beta1.MutatingWebhookConfiguration{} +} + +func testService() client.Object { + return &corev1.Service{} +} + +func testDeployment() client.Object { + return &appsv1.Deployment{} +} + +func testServiceAccount() client.Object { + return &corev1.ServiceAccount{} +} + +func testClusterRole() client.Object { + return &rbacv1.ClusterRole{} +} + +func testClusterRoleBinding() client.Object { + return &rbacv1.ClusterRoleBinding{} +} diff --git a/internal/selfhosted/webhook/certificate.go b/internal/selfhosted/webhook/certificate.go index 5c0c10b..bc08fa1 100644 --- a/internal/selfhosted/webhook/certificate.go +++ b/internal/selfhosted/webhook/certificate.go @@ -13,30 +13,30 @@ import ( "k8s.io/apimachinery/pkg/types" ) -type TlsCredentials struct { +type TlsCredential struct { privateKey []byte certificate []byte } -func (t *TlsCredentials) CaBundle() string { +func (t TlsCredential) CaBundle() string { return base64.StdEncoding.EncodeToString(t.certificate) } -func (t *TlsCredentials) Certificate() []byte { +func (t TlsCredential) Certificate() []byte { return t.certificate } -func (t *TlsCredentials) PrivateKey() []byte { +func (t TlsCredential) PrivateKey() []byte { return t.privateKey } -func CreateTlsCredential(serviceNamespacedName types.NamespacedName) (TlsCredentials, error) { +func CreateTlsCredential(serviceNamespacedName types.NamespacedName) (TlsCredential, error) { certificatePeriod := 365 // days // Generate RSA private key privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return TlsCredentials{}, err + return TlsCredential{}, err } // Define certificate template @@ -56,7 +56,7 @@ func CreateTlsCredential(serviceNamespacedName types.NamespacedName) (TlsCredent // Create the certificate certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { - return TlsCredentials{}, err + return TlsCredential{}, err } // Encode the private key to PEM format @@ -71,5 +71,5 @@ func CreateTlsCredential(serviceNamespacedName types.NamespacedName) (TlsCredent Bytes: certBytes, }) - return TlsCredentials{privateKey: privPemBytes, certificate: certPemBytes}, nil + return TlsCredential{privateKey: privPemBytes, certificate: certPemBytes}, nil } diff --git a/internal/selfhosted/webhook/deployment.yaml b/internal/selfhosted/webhook/deployment.yaml deleted file mode 100644 index 97b776e..0000000 --- a/internal/selfhosted/webhook/deployment.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: pod-identity-webhook - namespace: kube-system -spec: - replicas: 1 - selector: - matchLabels: - app: pod-identity-webhook - template: - metadata: - labels: - app: pod-identity-webhook - spec: - serviceAccountName: pod-identity-webhook - containers: - - name: pod-identity-webhook - image: quay.io/amis/pod-identity-webhook:v0.0.1 - imagePullPolicy: Always - command: - - /webhook - - --in-cluster - - --namespace=kube-system - - --service-name=pod-identity-webhook - - --tls-secret=pod-identity-webhook - - --annotation-prefix=eks.amazonaws.com - - --token-audience=sts.amazonaws.com - - --logtostderr - volumeMounts: - - name: webhook-certs - mountPath: /var/run/app/certs - readOnly: false - volumes: - - name: webhook-certs - emptyDir: {} \ No newline at end of file diff --git a/internal/selfhosted/webhook/mutatingwebhook-ca-bundle.yaml b/internal/selfhosted/webhook/mutatingwebhook-ca-bundle.yaml deleted file mode 100644 index c002c01..0000000 --- a/internal/selfhosted/webhook/mutatingwebhook-ca-bundle.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: admissionregistration.k8s.io/v1beta1 -kind: MutatingWebhookConfiguration -metadata: - name: pod-identity-webhook - namespace: kube-system -webhooks: -- name: pod-identity-webhook.amazonaws.com - failurePolicy: Ignore - clientConfig: - service: - name: pod-identity-webhook - namespace: kube-system - path: "/mutate" - caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURQekNDQWllZ0F3SUJBZ0lVTjVSVnlOTHRhM2pxZkxiS2I5VmFBY0JZaDFnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0x6RXRNQ3NHQTFVRUF3d2tjRzlrTFdsa1pXNTBhWFI1TFhkbFltaHZiMnN1YTNWaVpTMXplWE4wWlcwdQpjM1pqTUI0WERUSTBNRE13T1RFd016VXlOMW9YRFRJMU1ETXdPVEV3TXpVeU4xb3dMekV0TUNzR0ExVUVBd3drCmNHOWtMV2xrWlc1MGFYUjVMWGRsWW1odmIyc3VhM1ZpWlMxemVYTjBaVzB1YzNaak1JSUJJakFOQmdrcWhraUcKOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXpSYzY0d2V0TzBSVFNOT2V2c3dqd0JpQjZjMFRaKzRubWhYeQpnVEVxNk9MUXZ5R1k4SlBwKzZTZG1qaGFlRGd2SGxmOU1NODNxa2FzdlBzY0Znd0lkOThwQUhwKzhkdEg3RTlNCjZNNCtDZm5LU2V3QmIrQ25YN0lkeGN2Z3hvSE5rMDcvbERhaXJ5TEVTaFprcnphaW9TYW9rb0dIdStaQmhONHYKdVBrK0xiTGVoSW9QUXdYcm9pMHU3dW4yd2NQaExNSENMckZzUzFTQ3pCMm1xdWN1ZFNNOFNOSWpPUGRRVE90VwpNQmNpMmU2MENQTEEzUWVFUkZnbXdzMEVnV3dyc2ZlVjB5ay83SjBTbFJoVStvQWdJajdLSWlxblFjZU54VFl1CmRrMndaL1JDM0wxck40S0xtb1ArS2dWQU9LK3Q4VjlNbVRzVE9pTHJ3WXhnOE14RWVRSURBUUFCbzFNd1VUQWQKQmdOVkhRNEVGZ1FVQzdxOTcxMDk1V3NLdVpFZ0RiRm9SUVZ6M2N3d0h3WURWUjBqQkJnd0ZvQVVDN3E5NzEwOQo1V3NLdVpFZ0RiRm9SUVZ6M2N3d0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DCkFRRUFsOU92Z2Z6eXZSSVFzVXU5ckFMS3psS2FWa0xDTUczczlpblR1c3M1YkRBY2t2cTM3Y04wQzdzd1Q4TU4KTzhNcU9mS3lHa2xSWHZCT1pCZ2tGYmx6U0xPREhORFpnSXZacmYxM2JrK1lCUG5ZOVdyeWVidzFHd0lUWTdNQwo1ZFhyYm9JOWtaOUY1c0NiMzNtYTZFdVhKVlpvS0JFc2U3Z20yckhsOVF4d253NVNrbDFtN2QxeS9nQS9YUll0CmJIbkkzVjlycHpzb0grZzYrdjBxU1Y2dDVHYVpsbW0veFJ5YzNKRmtvaU5xQk44MlVwMTFNUlhjd04wTlpSN2oKZjVEVFJxVHdSKzAzT0I1T1k2R2tyclkrK1IybGdKUk1VNE91aHl2cjhqY1N6Q0tNY2dZMjJkc2d5aThTYjFYMQorL3hUcC80VVpSaU5IQUlGcnlaMFY1RlpvQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - rules: - - operations: [ "CREATE" ] - apiGroups: [""] - apiVersions: ["v1"] - resources: ["pods"] \ No newline at end of file diff --git a/internal/selfhosted/webhook/mutatingwebhook.yaml b/internal/selfhosted/webhook/mutatingwebhook.yaml deleted file mode 100644 index a05e69e..0000000 --- a/internal/selfhosted/webhook/mutatingwebhook.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: admissionregistration.k8s.io/v1beta1 -kind: MutatingWebhookConfiguration -metadata: - name: pod-identity-webhook - namespace: kube-system -webhooks: -- name: pod-identity-webhook.amazonaws.com - failurePolicy: Ignore - clientConfig: - service: - name: pod-identity-webhook - namespace: kube-system - path: "/mutate" - caBundle: ${CA_BUNDLE} - rules: - - operations: [ "CREATE" ] - apiGroups: [""] - apiVersions: ["v1"] - resources: ["pods"] \ No newline at end of file diff --git a/internal/selfhosted/webhook/testdata/clusterrole.yaml b/internal/selfhosted/webhook/testdata/clusterrole.yaml new file mode 100644 index 0000000..d387daa --- /dev/null +++ b/internal/selfhosted/webhook/testdata/clusterrole.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pod-identity-webhook +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - get + - update + - patch + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - watch + - list + - apiGroups: + - certificates.k8s.io + resources: + - certificatesigningrequests + verbs: + - create + - get + - list + - watch diff --git a/internal/selfhosted/webhook/testdata/clusterrolebinding.yaml b/internal/selfhosted/webhook/testdata/clusterrolebinding.yaml new file mode 100644 index 0000000..afe83e7 --- /dev/null +++ b/internal/selfhosted/webhook/testdata/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: pod-identity-webhook +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: pod-identity-webhook +subjects: + - kind: ServiceAccount + name: pod-identity-webhook + namespace: kube-system diff --git a/internal/selfhosted/webhook/testdata/deployment.yaml b/internal/selfhosted/webhook/testdata/deployment.yaml new file mode 100644 index 0000000..ab80396 --- /dev/null +++ b/internal/selfhosted/webhook/testdata/deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pod-identity-webhook + namespace: kube-system +spec: + replicas: 1 + selector: + matchLabels: + app: pod-identity-webhook + template: + metadata: + labels: + app: pod-identity-webhook + spec: + serviceAccountName: pod-identity-webhook + containers: + - name: pod-identity-webhook + image: quay.io/amis/pod-identity-webhook:v0.0.1 + imagePullPolicy: Always + # command: + # - /webhook + # - --in-cluster + # - --namespace=kube-system + # - --service-name=pod-identity-webhook + # - --tls-secret=pod-identity-webhook + # - --annotation-prefix=eks.amazonaws.com + # - --token-audience=sts.amazonaws.com + # - --logtostderr + volumeMounts: + - name: webhook-certs + mountPath: /var/run/app/certs + readOnly: false + volumes: + - name: webhook-certs + emptyDir: {} diff --git a/internal/selfhosted/webhook/testdata/mutatingwebhook.yaml b/internal/selfhosted/webhook/testdata/mutatingwebhook.yaml new file mode 100644 index 0000000..c1820eb --- /dev/null +++ b/internal/selfhosted/webhook/testdata/mutatingwebhook.yaml @@ -0,0 +1,18 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingWebhookConfiguration +metadata: + name: pod-identity-webhook + namespace: kube-system +webhooks: + - name: pod-identity-webhook.amazonaws.com + failurePolicy: Ignore + clientConfig: + service: + name: pod-identity-webhook + namespace: kube-system + path: "/mutate" + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] diff --git a/internal/selfhosted/webhook/service.yaml b/internal/selfhosted/webhook/testdata/service.yaml similarity index 100% rename from internal/selfhosted/webhook/service.yaml rename to internal/selfhosted/webhook/testdata/service.yaml diff --git a/internal/selfhosted/webhook/testdata/serviceaccount.yaml b/internal/selfhosted/webhook/testdata/serviceaccount.yaml new file mode 100644 index 0000000..5203071 --- /dev/null +++ b/internal/selfhosted/webhook/testdata/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pod-identity-webhook + namespace: kube-system diff --git a/internal/selfhosted/webhook/webhook.go b/internal/selfhosted/webhook/webhook.go new file mode 100644 index 0000000..7930b39 --- /dev/null +++ b/internal/selfhosted/webhook/webhook.go @@ -0,0 +1,68 @@ +package webhook + +import ( + "fmt" + + "github.com/kkb0318/irsa-manager/internal/manifests" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type AwsWebhook struct { + resources []client.Object +} + +func secretNamespacedName() types.NamespacedName { + return types.NamespacedName{ + Name: "pod-identity-webhook", + Namespace: WEBHOOK_NAMESPACE, + } +} + +func NewWebHook() (*AwsWebhook, error) { + factory := newBaseManifestFactory() + resources, err := myCertificate(factory) + if err != nil { + return nil, err + } + return &AwsWebhook{resources}, nil +} + +func myCertificate(base *baseManifestFactory) ([]client.Object, error) { + tlsCredential, err := CreateTlsCredential(serviceNamespacedName()) + if err != nil { + return nil, err + } + resources := []client.Object{} + secretNamespacedName := secretNamespacedName() + secret, err := manifests.NewSecretBuilder(). + WithCertificate(tlsCredential). + Build(secretNamespacedName) + if err != nil { + return nil, err + } + + deploy := base.deployment() + deploy.Spec.Template.Spec.Containers[0].Command = []string{ + "/webhook", + "--in-cluster", + fmt.Sprintf("--namespace=%s", WEBHOOK_NAMESPACE), + fmt.Sprintf("--service-name=%s", base.serviceMeta.Name), + fmt.Sprintf("--tls-secret=%s", secretNamespacedName.Name), + "--annotation-prefix=eks.amazonaws.com", + "--token-audience=sts.amazonaws.com", + "--logtostderr", + } + mutate := base.mutatingWebhookConfiguration() + mutate.Webhooks[0].ClientConfig.CABundle = []byte(tlsCredential.CaBundle()) + resources = append(resources, + secret, + deploy, + mutate, + base.clusterRole(), + base.clusterRoleBinding(), + base.serviceAccount(), + base.service(), + ) + return resources, nil +}