diff --git a/docs/kubernetes.md b/docs/kubernetes.md index c4b8b67..84b5197 100644 --- a/docs/kubernetes.md +++ b/docs/kubernetes.md @@ -98,9 +98,144 @@ You don't need to expose any port in your container. | easyhaproxy.redirect | (optional) JSON. Key pair with a domain and its destination. | *empty* | \{"domain":"redirect_url"} | | easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp | | easyhaproxy.listen_port | (optional) Override the HTTP listen port created for that ingress | 80 | 8081 | +| easyhaproxy.plugins | (optional) Comma-separated list of plugins to enable for this ingress | *empty* | cloudflare,deny_pages | +| easyhaproxy.plugin.{name}.{key} | (optional) Plugin-specific configuration (see [Using Plugins](plugins.md)) | *varies* | See examples below | **Important**: The annotations are per ingress and applied to all hosts in that ingress configuration. +## Using Plugins with Kubernetes + +Plugins extend HAProxy configuration with additional functionality like JWT validation, IP whitelisting, or Cloudflare IP restoration. For a complete list of available plugins, see the [Using Plugins](plugins.md) guide. + +### Enabling Plugins for an Ingress + +Add the `easyhaproxy.plugins` annotation with a comma-separated list of plugin names: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "cloudflare,deny_pages" + name: example-ingress + namespace: example +spec: + rules: + - host: example.org + http: + paths: + - backend: + service: + name: example-service + port: + number: 8080 + pathType: ImplementationSpecific +``` + +### Configuring Plugin Options + +Use `easyhaproxy.plugin.{plugin_name}.{option}` annotations to configure individual plugins: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "deny_pages" + easyhaproxy.plugin.deny_pages.paths: "/admin,/private,/config" + easyhaproxy.plugin.deny_pages.status_code: "403" + name: secure-app-ingress + namespace: production +spec: + rules: + - host: myapp.example.com + http: + paths: + - backend: + service: + name: myapp-service + port: + number: 8080 + pathType: ImplementationSpecific +``` + +### Common Plugin Examples + +**Protect API with JWT validation:** + +```yaml +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "jwt_validator" + easyhaproxy.plugin.jwt_validator.algorithm: "RS256" + easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/" + easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com" + easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" +``` + +**Note:** For JWT validation, you'll need to mount the public key file into the EasyHAProxy pod. See [Using Plugins](plugins.md#jwt-validator-plugin-domain) for details. + +**Restrict access to specific IPs:** + +```yaml +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "ip_whitelist" + easyhaproxy.plugin.ip_whitelist.allowed_ips: "192.168.1.0/24,10.0.0.5" + easyhaproxy.plugin.ip_whitelist.status_code: "403" +``` + +**Restore Cloudflare visitor IPs:** + +```yaml +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "cloudflare" +``` + +**Multiple plugins together:** + +```yaml +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + easyhaproxy.plugins: "cloudflare,deny_pages" + easyhaproxy.plugin.deny_pages.paths: "/wp-admin,/wp-login.php" + easyhaproxy.plugin.deny_pages.status_code: "404" +``` + +### Global Plugin Configuration + +Some plugins (like `cleanup`) are global and execute once per discovery cycle. Configure these via environment variables or YAML configuration: + +**Using Helm values.yaml:** + +```yaml +easyhaproxy: + plugins: + enabled: cleanup + config: + cleanup: + max_idle_time: 600 +``` + +**Using environment variables:** + +```yaml +env: + - name: EASYHAPROXY_PLUGINS_ENABLED + value: "cleanup" + - name: EASYHAPROXY_PLUGIN_CLEANUP_MAX_IDLE_TIME + value: "600" +``` + +For more information on plugin types and available plugins, see the [Using Plugins](plugins.md) guide. + ## Certbot / ACME / Letsencrypt It is necessary add the annotation `easyhaproxy.certbot` to the ingress configuration: diff --git a/docs/plugins.md b/docs/plugins.md index 24a1682..db4beb5 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -236,11 +236,52 @@ http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn ## Configuration Methods -Plugins can be configured using three methods, listed in order of precedence (highest to lowest): +Plugins can be configured using different methods depending on your deployment environment: -### 1. Container Labels (Domain Plugins Only) +### 1. Kubernetes Annotations (Ingress Resources) -Enable and configure domain plugins for specific containers: +Enable and configure domain plugins for specific Kubernetes ingresses: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: easyhaproxy-ingress + # Enable plugins + easyhaproxy.plugins: "jwt_validator,deny_pages" + # Configure jwt_validator plugin + easyhaproxy.plugin.jwt_validator.algorithm: "RS256" + easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/" + easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com" + easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" + # Configure deny_pages plugin + easyhaproxy.plugin.deny_pages.paths: "/admin,/private" + easyhaproxy.plugin.deny_pages.status_code: "403" + name: api-ingress + namespace: production +spec: + rules: + - host: api.example.com + http: + paths: + - backend: + service: + name: api-service + port: + number: 8080 + pathType: ImplementationSpecific +``` + +**Annotation format:** +- Enable plugins: `easyhaproxy.plugins: plugin1,plugin2` +- Configure plugin: `easyhaproxy.plugin..: value` + +See the [Kubernetes guide](kubernetes.md#using-plugins-with-kubernetes) for more examples. + +### 2. Container Labels (Docker/Docker Compose) + +Enable and configure domain plugins for specific Docker containers: ```yaml services: @@ -262,7 +303,7 @@ services: **Where `` is:** `http`, `https`, `tcp`, etc. -### 2. Static YAML Configuration +### 3. Static YAML Configuration Configure plugins globally in `/etc/haproxy/static/config.yaml`: @@ -288,7 +329,7 @@ plugins: enabled: false # Disable globally, enable per-container via labels ``` -### 3. Environment Variables +### 4. Environment Variables Configure plugins via environment variables: @@ -461,11 +502,18 @@ DEBUG: Plugin cloudflare metadata: {'domain': 'example.com', 'ip_list_path': '/e ### Configuration Not Applied **Check precedence order:** + +For Kubernetes deployments: +1. Ingress annotations (highest) +2. YAML configuration +3. Environment variables (lowest) + +For Docker deployments: 1. Container labels (highest) 2. YAML configuration 3. Environment variables (lowest) -Container labels override YAML and env vars. +Per-ingress/per-container settings override global configuration. ### Plugin Output Missing diff --git a/src/processor/__init__.py b/src/processor/__init__.py index 8daf5a2..0695a46 100644 --- a/src/processor/__init__.py +++ b/src/processor/__init__.py @@ -251,6 +251,13 @@ class Kubernetes(ProcessorInterface): redirect = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.redirect") mode = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.mode") listen_port = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.listen_port", 80) + plugins = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.plugins") + + # Extract plugin-specific configurations + plugin_annotations = {} + for annotation_key, annotation_value in ingress.metadata.annotations.items(): + if annotation_key.startswith("easyhaproxy.plugin."): + plugin_annotations[annotation_key] = annotation_value data = {"creation_timestamp": ingress.metadata.creation_timestamp.strftime("%x %X"), "resource_version": ingress.metadata.resource_version, "namespace": ingress.metadata.namespace} @@ -297,6 +304,16 @@ class Kubernetes(ProcessorInterface): rule_data["%s.mode" % definition] = mode rule_data["%s.balance" % definition] = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.balance", "roundrobin") + # Add plugin configuration + if plugins is not None: + rule_data["%s.plugins" % definition] = plugins + + # Add plugin-specific configurations + for plugin_key, plugin_value in plugin_annotations.items(): + # Convert easyhaproxy.plugin.X.Y to easyhaproxy.{definition}.plugin.X.Y + plugin_config_key = plugin_key.replace("easyhaproxy.plugin.", "%s.plugin." % definition) + rule_data[plugin_config_key] = plugin_value + service_name = rule.http.paths[0].backend.service.name try: api_response = self.api_instance.read_namespaced_service(service_name, ingress.metadata.namespace)