TL;DR: The Gardener ACL extension allows you to limit the access to shoot clusters using an allow-list mechanism. Basically, it looks like this:
# in the shoot object
spec:
extensions:
- type: acl
providerConfig:
rule:
action: ALLOW
type: remote_ip
cidrs:
- "1.2.3.4/24"
- "10.250.0.0/16"
- ...
The extension also supports multiple ingress namespaces, e.g. when using
Gardener ExposureClasses
or deploying Highly Available Control Planes (see
ADR03 for more information).
Please read on for more information.
Set your KUBECONFIG
variable to the Garden cluster.
kubectl apply -f deploy/extension/base/controller-registration.yaml
Gardener introduced Shoot API Server SNI with GEP08.
Using Istio, Gardener configures a single ingress gateway per seed to proxy traffic to all API servers on this seed based on some criteria. At it's core, Istio configures an envoy proxy using a set of Kubernetes CRDs. We can hook into this mechanism and insert additional configuration, which further limits the access to a specific cluster.
Broadly speaking, there are three different external traffic flows:
- Kubernetes API Listener (via SNI name)
- Kubernetes Service Listener (internal flow)
- VPN Listener
These ways are described in more detail in the aforementioned GEP. Essentially, these three ways are all represented by a specific Envoy listener with filters. The extension needs to hook into each of these filters (and their filter chains) to implement the desired behavior. Unfortunately, all three types of access require a unique way of handling them, respectively.
- SNI Access - The most straightforward approach. Wen can deploy one
additional
EnvoyFilter
per shoot with enabled ACL extension. It contains a filter patch that matches on the shoot SNI name and specifies anALLOW
rule with the provided IPs. - Internal Flow - Gardener creates one
EnvoyFilter
per shoot that defines this listener. Unfortunately, it doesn't have any criteria we could use to match it with an additionalEvnoyFilter
spec on a per-shoot basis, and we've tried a lot of things to make it work. On top of that, a behavior that we see as a bug in Istio prevents us from working with priorities here, which was the closest we got to make it work. Now instead, the extension deploys aMutatingWebhook
that intercepts creations and updates ofEnvoyFilter
resources starting withshoot--
(which is their only common feature). We then insert our rules. To make this work with updates toExtension
objects, the controller dealing with 1) also updates a hash annotation on theseEnvoyFilter
resources every time the respective ACL extension object is updated. - VPN Access - All VPN traffic moves through the same listener. This
requires us to create only a single
EnvoyFilter
for VPN that contains all rules of all shoots that have the extension enabled. And, conversely, we need to make sure that traffic of all shoots that don't have the extension enabled is still able to pass through this filter unhindered. We achieve this by not only creating a policy for every shoot with ACL enabled, but also an "inverted" policy which matches all shoots that don't have ACL enabled. All these policies are then put in a single EnvoyFilter patch.
Because of the last point, we currently see no way of allowing the user to
define multiple rules of different action types (ALLOW
or DENY
). Instead, we
only support a single ALLOW
rule per shoot, which is in our opinion the best
trade-off to efficiently secure Kubernetes API servers.
See ADR02 for a more in-depth discussion of the challenges we had.
In order for the internal VPN traffic to work, the router IP adresses from the shoot openstack projects have to get allowlisted in the ACL extension.
Gardener provides a Health Check Library
that we can use to monitor the health of resources that our extension is
responsible for. Example: If the extension controller deploys a Gardener
ManagedResource
, we can define a health check on the extension that checks for
the health of this ManagedResource
. This lets the extension reflect the state
of the resources it is responsible for. This is expressed by status conditions
in the extension resource itself (one per health check).
Extensions are installed on a Gardener cluster by deploying a
ControllerRegistration
and a ControllerDeployment
object to the garden
cluster. In this repository, you find an example for both of these resources in
the deploy/extension/base/controller-registration.yaml
file.
The ProviderConfig.chart
field contains the entire Helm chart for your
extension as a gzipped and then base64-encoded string. After you altered this
Helm chart in the charts/gardener-extension-acl
directory, run make generate
to
re-create this value.
To run the test suite, execute:
make test
Place all needed Gardener CRDs in the upstream-crds
directory, they get
installed automatically in the envtest cluster.
See the actuator_test.go for a minimal test case example.
Set up a garden local-setup.
To install the extension with 2 ways:
make extension-up
this will install the acl-extension into the local gardener environment.
make extension-dev
this will also install the acl-extension into the local gardener environment but it will rebuild and redeploy if you press any key in the terminal.
This can only be done with the gardener local-setup.
After your local gardener is ready you can start the controller
Install extension
make extension-up
Disable reconcile of managed resource
kubectl annotate managedresource acl-XXXXXX resources.gardener.cloud/ignore="true"
Scale down acl-extension:
kubectl scale deployment -n extension-acl-XXXXXXX --replicas=0 gardener-extension-acl
Now you can run the acl-extension locally to debug it.
make run