{"id":13448965,"url":"https://github.com/elithrar/admission-control","last_synced_at":"2025-05-10T21:11:32.732Z","repository":{"id":47632024,"uuid":"193375886","full_name":"elithrar/admission-control","owner":"elithrar","description":"A helpful micro-framework for writing Kubernetes Admission Controllers 🔎🎟 ","archived":false,"fork":false,"pushed_at":"2023-02-25T03:06:41.000Z","size":175,"stargazers_count":172,"open_issues_count":6,"forks_count":13,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-06T03:57:15.980Z","etag":null,"topics":["admission","admission-controller","admission-webhook","controller","golang","k8s","kubernetes","webhooks"],"latest_commit_sha":null,"homepage":"https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elithrar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-06-23T17:19:50.000Z","updated_at":"2024-09-03T13:42:29.000Z","dependencies_parsed_at":"2024-01-18T17:08:38.735Z","dependency_job_id":null,"html_url":"https://github.com/elithrar/admission-control","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elithrar%2Fadmission-control","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elithrar%2Fadmission-control/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elithrar%2Fadmission-control/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elithrar%2Fadmission-control/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elithrar","download_url":"https://codeload.github.com/elithrar/admission-control/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253309681,"owners_count":21888034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["admission","admission-controller","admission-webhook","controller","golang","k8s","kubernetes","webhooks"],"created_at":"2024-07-31T06:00:26.479Z","updated_at":"2025-05-09T19:22:34.930Z","avatar_url":"https://github.com/elithrar.png","language":"Go","funding_links":[],"categories":["Go","Framework"],"sub_categories":[],"readme":"# Admission Control\n\n🕵️🕵️🕵️\n\n[![GoDoc](https://godoc.org/github.com/elithrar/admission-control?status.svg)](https://godoc.org/github.com/elithrar/admission-control)\n[![CircleCI](https://circleci.com/gh/elithrar/admission-control.svg?style=svg)](https://circleci.com/gh/elithrar/admission-control)\n\nA micro-framework for building and deploying dynamic [Admission Controllers](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/) for your Kubernetes clusters. It reduces the boilerplate needed to inspect, validate and/or reject the admission of objects to your cluster, allowing you to focus on writing the specific business logic you want to enforce.\n\n- Can be used as the target of both [`ValidatingWebhookConfiguration`]() and\n  [`MutatingWebhookConfiguration`]() - handlers can return simple allow/deny\n  responses, or patches (mutations) to submitted resources.\n- Provides an extensible `AdmissionHandler` type that accepts a custom\n  admission function (called an `AdmitFunc`), making it easy for you to add new\n  validating or mutating webhook endpoints.\n- Provides sample `Deployment`, `Service` and\n  `ValidatingWebhookConfiguration` definitions for you to build off of, and an\n  [`example webhook server`](https://github.com/elithrar/admission-control/tree/master/examples/admissiond)\n  as additional guidance.\n\n---\n\n- [Using the Framework](#using-the-framework)\n- [Built-In AdmitFuncs](#built-in-admitfuncs)\n- [Creating Your Own AdmitFunc](#creating-your-own-admitfunc)\n- [Configuring \u0026amp; Deploying a Server](#configuring--deploying-a-server)\n- [Pre-requisites](#pre-requisites)\n- [Setup](#setup)\n  - [Configuring a Server](#configuring-a-server)\n  - [Generating TLS Certificates](#generating-tls-certificates)\n  - [Deploying the Admission Controller](#deploying-the-admission-controller)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Using the Framework\n\n### Built-In AdmitFuncs\n\nAdmission Control provides a number of useful built-in [**AdmitFuncs**](https://godoc.org/github.com/elithrar/admission-control#AdmitFunc), including:\n\n- `EnforcePodAnnotations` - ensures that admitted Pods have (at least) the\n  required set of annotations. Annotation _values_ are matched using a\n  `matchFunc` (a `func(string) bool`) that allows flexible matching. For\n  example, a matchFunc could wrap the\n  [`IsDomainName`](https://godoc.org/github.com/miekg/dns#IsDomainName)\n  function from `miekg/dns`, or reference a `[]string` of accepted values. It\n  is strongly suggested you use a\n  [`namespaceSelector`](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/#webhook-v1beta1-admissionregistration)\n  as part of your webhook configuration to only apply this to specific\n  namespaces, and/or set the `ignoreNamespaces` argument to include\n  `kube-system`, as annotation validation will otherwise include system Pods.\n- `DenyPublicLoadBalancers` - prevents exposing `Services` of `type: LoadBalancer` outside of the cluster, instead requiring the LB to be\n  annotated as internal-only, by looking for the well-known annotations for\n  major cloud providers.\n- `DenyIngresses` - similar to the above, it prevents creating Ingresses\n  (except in the namespaces you allow). This can be useful for limiting which\n  namespaces can expose services via common Ingress types.\n\nMore built-ins are coming soon, and suggestions are welcome! ⏳\n\n### Creating Your Own AdmitFunc\n\nThe core type of the library is the [`AdmitFunc`](https://godoc.org/github.com/elithrar/admission-control#AdmitFunc) - a function that takes a k8s `AdmissionReview` object and returns an `(*AdmissionResponse, error)` tuple. You can provide a closure that returns an `AdmitFunc` type if you need to inject additional dependencies into your handler, and/or use a constructor function to do the same.\n\nThe `AdmissionReview` type wraps the [`AdmissionRequest`](https://godoc.org/k8s.io/api/admission/v1beta1#AdmissionRequest), which can be serialized into a concrete type—such as a `Pod` or `Service`—and subsequently validated.\n\nAn example `AdmitFunc` looks like this:\n\n```go\n// DenyDefaultLoadBalancerSourceRanges denies any kind: Service of type:\n// LoadBalancer that does not explicitly set .spec.loadBalancerSourceRanges -\n// which defaults to 0.0.0.0/0 (e.g. Internet traffic, if routable).\n//\n// This prevents LoadBalancers from being accidentally exposed to the Internet.\nfunc DenyDefaultLoadBalancerSourceRanges() AdmitFunc {\n    // Return a function of type AdmitFunc\n    return func(admissionReview *admission.AdmissionReview) (*admission.AdmissionResponse, error) {\n        kind := admissionReview.Request.Kind.Kind\n        // Create an *admission.AdmissionResponse that denies by default.\n        resp := newDefaultDenyResponse()\n\n        // Create an object to deserialize our requests' object into\n        service := core.Service{}\n        deserializer := serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()\n        if _, _, err := deserializer.Decode(admissionReview.Request.Object.Raw, nil, \u0026service); err != nil {\n          return nil, err\n        }\n\n        // Allow non-LoadBalancer Services to pass through.\n        if service.Spec.Type != \"LoadBalancer\" {\n          resp.Allowed = true\n          resp.Result.Message = fmt.Sprintf(\n            \"received a non-LoadBalancer type (%s)\",\n            service.Spec.Type,\n          )\n          return resp, nil\n        }\n\n        // Inspect the service.Spec.LoadBalancerSourceRanges field\n        // If unset, reject it.\n        // Returning an error from an AdmitFunc will automatically deny admission of that requests' object.\n        if service.Spec.LoadBalancerSourceRanges == nil {\n          return resp, fmt.Errorf(\"LoadBalancers without explicitly configured LoadBalancerSourceRanges are not allowed.\")\n        }\n\n        // Set resp.Allowed to true before returning your AdmissionResponse\n        resp.Allowed = true\n        return resp, nil\n    }\n}\n```\n\nYou can see that we deserialize the raw object in our `AdmissionReview` into an object (based on its Kind), inspect and validate the fields we're interested in, and either return an error (rejecting admission) or set `resp.Allowed = true` and allow admission.\n\nTips:\n\n- Having your `AdmitFunc`s focus on \"one\" thing is best practice: it allows you to be more granular in how you apply constraints to your cluster\n- Returning an `AdmitFunc` from a constructor/closure will allow you to inject dependencies and/or configuration into your handler.\n\nYou can then create an [`AdmissionHandler`](https://godoc.org/github.com/elithrar/admission-control#AdmissionHandler) and pass it the `AdmitFunc`. Use your favorite HTTP router, and associate a path with your handler:\n\n```go\n\t// We're using \"gorilla/mux\" as our router here.\n\tr := mux.NewRouter().StrictSlash(true)\n\tadmissions := r.PathPrefix(\"/admission-control\").Subrouter()\n\tadmissions.Handle(\"/deny-default-load-balancer-source-ranges\", \u0026admissioncontrol.AdmissionHandler{\n\t\tAdmitFunc:  admissioncontrol.DenyDefaultLoadBalancerSourceRanges(),\n\t\tLogger:     logger,\n\t}).Methods(http.MethodPost)\n```\n\nThe example server [`admissiond`](https://github.com/elithrar/admission-control/tree/master/examples/admissiond) provides a more complete example of how to configure \u0026 serve your admission controller endpoints.\n\n---\n\n## Configuring \u0026 Deploying a Server\n\nThere are two ways to deploy an admission controller:\n\n1. Within your Kubernetes cluster (\"in-cluster\"), where it runs as a Pod and is exposed as a Service to the rest of the cluster. This requires you to provision a TLS keypair, as admission controllers can only be accessed over TLS (HTTPS).\n2. Out-of-cluster, where it is accessible over HTTPS by the cluster. The admission controller could be hosted on another cluster, or more commonly, via a serverless platform like [Cloud Run](https://cloud.google.com/run/) or [Azure Container Instances](https://azure.microsoft.com/en-us/services/container-instances/). See the [`CloudRun.Dockerfile`](CloudRun.Dockerfile) for an example of how to build an image for Cloud Run.\n\nThe documentation below covers deploying within a Kubernetes cluster (option 1).\n\n### Pre-requisites\n\nYou'll need:\n\n- Access to a Kubernetes cluster (GKE, minikube, AKS, etc) with support for admission webhooks (v1.9+)\n- [`cfssl`](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#download-and-install-cfssl) as part of the process of generating a TLS key-pair, and some familiarity with creating TLS (SSL) certificates (CSRs, PEM-encoded certificates, keys).\n- Experience writing Go - for implementing your own `AdmitFuncs` (refer to the example `DenyPublicServices` AdmitFunc included).\n- Experience building OCI (Docker) containers via `docker build` or similar.\n\n## Setup\n\nSetting up an Admission Controller in your Kubernetes cluster has three major steps:\n\n1. Generate a TLS keypair—Kubernetes only allows HTTPS (TLS) communication to Admission Controllers, whether in-cluster or hosted externally—and make the key \u0026 certificate available as a `Secret` within your cluster.\n\n2. Create a `Deployment` with your Admission-Control-based server, mounting the TLS keypair in your `Secret` as a volume in the container.\n\n3. Configure a `ValidatingWebhookConfiguration` that tells Kubernetes which objects should be validated, and the endpoint (URL) on your `Service` to validate them against.\n\nYour single server can act as the admission controller for any number of `ValidatingWebhookConfiguration` or `MutatingWebhookConfiguration` - each configuration can point to a specific URL on the same server.\n\n## Configuring a Server\n\n\u003e ⚠ **Reminder**: Admission webhooks must support HTTPS (TLS) connections; k8s does not allow webhooks to be reached over plain-text HTTP. If running in-cluster, the Service fronting the controller must be reachable via TCP port 443. External webhooks only need to satisfy the HTTPS requirement, but can be reached on any valid TCP port.\n\nHaving your k8s cluster create a TLS certificate for you will dramatically simplify the configuration, as self-signed certificates require you to provide a `.webhooks.clientConfig.caBundle` value for verification.\n\nThe key steps include:\n\n1. Generate a TLS keypair for the admission controller by issuing a `CertificateSigningRequest` against the Kubernetes cluster, and obtain the CA certificate from the k8s cluster.\n2. Creating a `Deployment` and a `Service` that makes the admission controller available to the cluster.\n3. Creating a `ValidatingWebhookConfiguration` that points matching k8s API requests to a route on your admission controller. i.e. you may want to configure different validation policies between Services and Pods.\n\n---\n\n### Generating TLS Certificates\n\nAs noted above, we need to make our webhook endpoint available over HTTPS (TLS), which requires generating a CA cert (required as the `caBundle` value), key and certificate. You can can choose to [have your k8s cluster sign \u0026 provide a cert](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#create-a-certificate-signing-request) for you, or otherwise provide your own self-signed cert \u0026 CA cert.\n\nWe're going to have our cluster issue a certificate for us, which simplifies the process:\n\n1. Create a k8s [`CertificateSigningRequest`](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#create-a-certificate-signing-request) for the hostname(s) you will deploy the Service as. There is an example CSR in `demo-certs/csr.yaml` for the `admission-control-service.default.svc` hostname. This hostname must match the `.webhooks.name[].clientConfig.service.name` described in your `ValidatingWebhookConfiguration`.\n\n2. [Approve](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#approving-certificate-signing-requests) and then [fetch the certificate](https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster/#download-the-certificate-and-use-it) from the k8s API server.\n\n3. Create a `Secret` that [contains the TLS key-pair](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets) - the key you created alongside the CSR in step 1, and the certificate you fetched via `kubectl get csr \u003cname\u003e ...` - e.g. `kubectl create secret tls \u003cname\u003e --cert=cert.crt --key=key.key`.\n\n4. Retrieve the k8s cluster CA cert - this will be the `.webhooks.clientConfig.caBundle` value in our `ValidatingWebhookConfiguration`: `\n\n```sh\nkubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}'\n```\n\nSpecifically, you'll want to make sure your manifest looks like this:\n\n```yaml\napiVersion: admissionregistration.k8s.io/v1beta1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: deny-public-services\nwebhooks:\n  - name: deny-public-services.questionable.services\n    # \u003csnip, for brevity\u003e\n    clientConfig:\n      service:\n        # This is the hostname our certificate needs in its Subject Alternative\n        # Name array - name.namespace.svc\n        # If the certificate does NOT have this name, TLS validation will fail.\n        name: admission-control-service\n        namespace: default\n        path: \"/admission-control/deny-public-services\"\n      # This will be the CA cert from your k8s cluster, or the CA cert you\n      # generated if you took the DIY approach.\n      caBundle: \"\u003cyour-base64-encoded-PEM-certificate-here\u003e\"\n```\n\nWith the TLS certificates in hand, you can now move on to deploying the controller.\n\n---\n\n### Deploying the Admission Controller\n\nWith the TLS certificates generated \u0026 the associated `Secret` created, we can update our `Deployment`, `Service` and `ValidatingWebhookConfiguration` in-kind. Refer to the `samples/` directory if you need a reference config.\n\n1. Create a `Deployment` and make sure the `-host` flag passed to the admissiond container matches the hostname (ServerName) you used in the CSR.\n2. Update the `.spec.containers[].volumes.secret.secretName` to refer to the `Secret` you created in step 3.\n3. Create a `Service` that exposes the `Deployment` in step no. 4 to the cluster. Remember: the name of the Service should match one of the names in step 1.\n4. Create a `ValidatingWebhookConfiguration` that matches the objects (kinds, versions) and actions (create, update, delete), and configure the `.webhooks.clientConfig.service` map to point to the `Service` you created.\n\n\u003e Note: A set of example manifests - both `admissiond-deployment.yml` and `deny-public-admissions-config.yml`- are available in the `samples/` directory.\n\nTo deploy the built-in server to your cluster with its existing validation endpoints, you'll need to build the container image and push it to an image registry that your k8s cluster can access.\n\nIf you're using Google Container Registry, you can [push images to the same project](https://cloud.google.com/container-registry/docs/pushing-and-pulling) as your GKE cluster:\n\n```sh\ndocker build -t yourco/admissiond .\ndocker tag yourco/admissiond gcr.io/$PROJECTNAME/admissiond\ndocker push gcr.io/$PROJECTNAME/admissiond\n```\n\nMake sure to update/copy `samples/admission-control-service.yaml` with the new container image URL before deploying it:\n\n```sh\n# An example Deployment we'll try to expose\nkubectl apply -f samples/hello-app.yaml\n# Install the Admission Controller into the cluster\nkubectl apply -f samples/admission-control-service.yaml\n# Add our ValidatingWebhookConfiguration\nkubectl apply -f samples/deny-public-webhook-config.yaml\n```\n\nLet's now attempt to deploy a `kind: Service` of `type: LoadBalancer` without [the internal-only annotations](https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing#overview):\n\n```sh\nkubectl apply -f samples/public-service.yaml\n```\n\nYou should see the following output:\n\n```sh\nError from server (hello-service does not have the cloud.google.com/load-balancer-type: Internal annotation.): error when creating \"samples/public-service.yaml\": admission webhook \"deny-public-services.questionable.services\" denied the request: Services of type: LoadBalancer without an internal annotation are not allowed on this cluster\n```\n\nPerfect! 🎉\n\n---\n\n### Troubleshooting\n\nIf you run into problems setting up the admission-controller, make sure that:\n\n- Your certificates are valid / key-pairs match\n- You've inspected the Pod logs - e.g. via `kubectl logs -f -l app=admission-control` - all HTTP handler errors are logged to the configured logger.\n- Your `ValidatingWebhookConfiguration` is matching the right API versions, namespaces \u0026 objects vs. what you have configured as an `AdmitFunc` endpoint in the admission-control server.\n\nIf you're stuck, open an issue with the output of:\n\n```sh\nkubectl version\n# replace the label if you've authored your own Deployment manifest\nkubectl logs -f -l app=admission-control\n```\n\n... and any relevant error messages from attempting to `kubectl apply -f \u003cmanifest\u003e` that match your `ValidatingWebhookConfiguration`.\n\n## Contributing\n\nThis project is open to contributions!\n\nAs a courtesy: please open an issue with a brief proposal of your\nidea first (and the use-cases surrounding it!) before diving into\nimplementation.\n\n## License\n\nApache 2.0 licensed. Copyright Google, LLC (2019). See the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felithrar%2Fadmission-control","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felithrar%2Fadmission-control","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felithrar%2Fadmission-control/lists"}