Skip to content

Commit

Permalink
Merge pull request #92 from Kuadrant/external-auth
Browse files Browse the repository at this point in the history
External Auth ~ish
  • Loading branch information
didierofrivia authored Oct 2, 2024
2 parents 334088e + 4222ffb commit d5a96f7
Show file tree
Hide file tree
Showing 23 changed files with 3,012 additions and 558 deletions.
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,21 @@ chmod a+x $(1)/bin/protoc ;\
rm -rf $$TMP_DIR ;\
}
endef

##@ Util

# go-install-tool will 'go install' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-install-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef

include ./make/*.mk
148 changes: 132 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
A Proxy-Wasm module written in Rust, acting as a shim between Envoy and Limitador.

## Sample configuration

Following is a sample configuration used by the shim.

```yaml
failureMode: deny
rateLimitPolicies:
extensions:
auth-ext:
type: auth
endpoint: auth-cluster
failureMode: deny
ratelimit-ext:
type: ratelimit
endpoint: ratelimit-cluster
failureMode: deny
policies:
- name: rlp-ns-A/rlp-name-A
domain: rlp-ns-A/rlp-name-A
service: rate-limit-cluster
hostnames: ["*.toystore.com"]
hostnames: [ "*.toystore.com" ]
rules:
- conditions:
- allOf:
Expand All @@ -27,12 +35,15 @@ rateLimitPolicies:
- selector: request.method
operator: eq
value: GET
data:
- selector:
selector: request.headers.My-Custom-Header
- static:
key: admin
value: "1"
actions:
- extension: ratelimit-ext
scope: rlp-ns-A/rlp-name-A
data:
- selector:
selector: request.headers.My-Custom-Header
- static:
key: admin
value: "1"
```
## Features
Expand All @@ -57,6 +68,7 @@ pub enum WhenConditionOperator {

The `matches` operator is a a simple globbing pattern implementation based on regular expressions.
The only characters taken into account are:

* `?`: 0 or 1 characters
* `*`: 0 or more characters
* `+`: 1 or more characters
Expand Down Expand Up @@ -95,10 +107,10 @@ Example:
Input: this.is.a.exam\.ple -> Retuns: ["this", "is", "a", "exam.ple"].
```

Some path segments include dot `.` char in them. For instance envoy filter names: `envoy.filters.http.header_to_metadata`.
Some path segments include dot `.` char in them. For instance envoy filter
names: `envoy.filters.http.header_to_metadata`.
In that particular cases, the dot chat (separator), needs to be escaped.


## Building

Prerequisites:
Expand Down Expand Up @@ -127,7 +139,108 @@ make build BUILD=release
cargo test
```

## Running local development environment
## Running local development environment (kind)

`docker` is required.

Run local development environment

```sh
make local-setup
```

This deploys a local kubernetes cluster using kind, with the local build of wasm-shim mapped to the envoy container. An
echo API as well as limitador, authorino, and some test policies are configured.

To expose the envoy endpoint run the following:

```sh
kubectl port-forward --namespace default deployment/envoy 8000:8000
```

There is then a single auth policy defined for e2e testing:

* `auth-a` which defines auth is required for requests to `/get` for the `AuthConfig` with `effective-route-1`

```sh
curl -H "Host: test.a.auth.com" http://127.0.0.1:8000/get -i
# HTTP/1.1 401 Unauthorized
```

```sh
curl -H "Host: test.a.auth.com" -H "Authorization: APIKEY IAMALICE" http://127.0.0.1:8000/get -i
# HTTP/1.1 200 OK
```

And three rate limit policies defined for e2e testing:

* `rlp-a`: Only one data item. Data selector should not generate return any value. Thus, descriptor should be empty and
rate limiting service should **not** be called.

```sh
curl -H "Host: test.a.rlp.com" http://127.0.0.1:8000/get -i
```

* `rlp-b`: Conditions do not match. Hence, rate limiting service should **not** be called.

```sh
curl -H "Host: test.b.rlp.com" http://127.0.0.1:8000/get -i
```

* `rlp-c`: Four descriptors from multiple rules should be generated. Hence, rate limiting service should be called.

```sh
curl -H "Host: test.c.rlp.com" -H "x-forwarded-for: 127.0.0.1" -H "My-Custom-Header-01: my-custom-header-value-01" -H "x-dyn-user-id: bob" http://127.0.0.1:8000/get -i
```

* `multi-a` which defines two actions for authenticated ratelimiting.

```sh
curl -H "Host: test.a.multi.com" http://127.0.0.1:8000/get -i
# HTTP/1.1 401 Unauthorized
```

Alice has 5 requests per 10 seconds:
```sh
while :; do curl --write-out '%{http_code}\n' --silent --output /dev/null -H "Authorization: APIKEY IAMALICE" -H "Host: test.a.multi.com" http://127.0.0.1:8000/get | grep -E --color "\b(429)\b|$"; sleep 1; done
```

Bob has 2 requests per 10 seconds:
```sh
while :; do curl --write-out '%{http_code}\n' --silent --output /dev/null -H "Authorization: APIKEY IAMBOB" -H "Host: test.a.multi.com" http://127.0.0.1:8000/get | grep -E --color "\b(429)\b|$"; sleep 1; done
```

The expected descriptors:

```
RateLimitDescriptor { entries: [Entry { key: "limit_to_be_activated", value: "1" }], limit: None }
```

```
RateLimitDescriptor { entries: [Entry { key: "source.address", value: "127.0.0.1:0" }], limit: None }
```

```
RateLimitDescriptor { entries: [Entry { key: "request.headers.My-Custom-Header-01", value: "my-custom-header-value-01" }], limit: None }
```

```
RateLimitDescriptor { entries: [Entry { key: "user_id", value: "bob" }], limit: None }
```

To rebuild and deploy to the cluster:

```sh
make build local-rollout
```

Stop and clean up resources:

```sh
make local-cleanup
```

## Running local development environment (docker-compose legacy)

`docker` and `docker-compose` required.

Expand All @@ -139,7 +252,8 @@ make development

Three rate limit policies defined for e2e testing:

* `rlp-a`: Only one data item. Data selector should not generate return any value. Thus, descriptor should be empty and rate limiting service should **not** be called.
* `rlp-a`: Only one data item. Data selector should not generate return any value. Thus, descriptor should be empty and
rate limiting service should **not** be called.

```
curl -H "Host: test.a.com" http://127.0.0.1:18000/get
Expand Down Expand Up @@ -175,7 +289,9 @@ RateLimitDescriptor { entries: [Entry { key: "request.headers.My-Custom-Header-0
RateLimitDescriptor { entries: [Entry { key: "user_id", value: "bob" }], limit: None }
```

**Note:** Using [Header-To-Metadata filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/header_to_metadata_filter#config-http-filters-header-to-metadata), `x-dyn-user-id` header value is available in the metadata struct with the `user-id` key.
**Note:**
Using [Header-To-Metadata filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/header_to_metadata_filter#config-http-filters-header-to-metadata), `x-dyn-user-id`
header value is available in the metadata struct with the `user-id` key.

According to the defined limits:

Expand All @@ -187,7 +303,7 @@ According to the defined limits:
conditions:
- "limit_to_be_activated == '1'"
- "user_id == 'bob'"
variables: []
variables: [ ]
```
The third request in less than 10 seconds should return `429 Too Many Requests`.
Expand Down
126 changes: 126 additions & 0 deletions make/deploy.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
##@ Kind

.PHONY: kind kind-create-cluster kind-delete-cluster

KIND = $(PROJECT_PATH)/bin/kind
KIND_VERSION = v0.23.0
$(KIND):
$(call go-install-tool,$(KIND),sigs.k8s.io/kind@$(KIND_VERSION))

kind: $(KIND) ## Download kind locally if necessary.

KIND_CLUSTER_NAME ?= wasm-auth-local

kind-create-cluster: BUILD?=debug
kind-create-cluster: WASM_PATH=$(subst /,\/,$(PROJECT_PATH)/target/wasm32-unknown-unknown/$(BUILD))
kind-create-cluster: kind ## Create the "wasm-auth-local" kind cluster.
@{ \
TEMP_FILE=/tmp/kind-cluster-$$(openssl rand -hex 4).yaml ;\
cp $(PROJECT_PATH)/utils/kind/cluster.yaml $$TEMP_FILE ;\
$(SED) -i "s/\$$(WASM_PATH)/$(WASM_PATH)/g" $$TEMP_FILE ;\
KIND_EXPERIMENTAL_PROVIDER=$(CONTAINER_ENGINE) $(KIND) create cluster --name $(KIND_CLUSTER_NAME) --config $$TEMP_FILE ;\
rm -rf $$TEMP_FILE ;\
}

kind-delete-cluster: ## Delete the "wasm-auth-local" kind cluster.
- KIND_EXPERIMENTAL_PROVIDER=$(CONTAINER_ENGINE) $(KIND) delete cluster --name $(KIND_CLUSTER_NAME)


##@ Authorino

.PHONY: install-authorino-operator certs deploy-authorino

AUTHORINO_IMAGE ?= quay.io/kuadrant/authorino:latest
AUTHORINO_OPERATOR_NAMESPACE ?= authorino-operator
install-authorino-operator: ## Installs Authorino Operator and dependencies into the Kubernetes cluster configured in ~/.kube/config
curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s -- --git-ref main
kubectl patch deployment/authorino-webhooks -n $(AUTHORINO_OPERATOR_NAMESPACE) -p '{"spec":{"template":{"spec":{"containers":[{"name":"webhooks","image":"$(AUTHORINO_IMAGE)","imagePullPolicy":"IfNotPresent"}]}}}}'
kubectl -n $(AUTHORINO_OPERATOR_NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all

TLS_ENABLED ?= true
AUTHORINO_INSTANCE ?= authorino
NAMESPACE ?= default
certs: sed ## Requests TLS certificates for the Authorino instance if TLS is enabled, cert-manager.io is installed, and the secret is not already present
ifeq (true,$(TLS_ENABLED))
ifeq (,$(shell kubectl -n $(NAMESPACE) get secret/authorino-oidc-server-cert 2>/dev/null))
curl -sl https://raw.githubusercontent.com/kuadrant/authorino/main/deploy/certs.yaml | $(SED) "s/\$$(AUTHORINO_INSTANCE)/$(AUTHORINO_INSTANCE)/g;s/\$$(NAMESPACE)/$(NAMESPACE)/g" | kubectl -n $(NAMESPACE) apply -f -
else
echo "tls cert secret found."
endif
else
echo "tls disabled."
endif

deploy-authorino: certs sed ## Deploys an instance of Authorino into the Kubernetes cluster configured in ~/.kube/config
@{ \
set -e ;\
TEMP_FILE=/tmp/authorino-deploy-$$(openssl rand -hex 4).yaml ;\
curl -sl https://raw.githubusercontent.com/kuadrant/authorino/main/deploy/authorino.yaml > $$TEMP_FILE ;\
$(SED) -i "s/\$$(AUTHORINO_INSTANCE)/$(AUTHORINO_INSTANCE)/g;s/\$$(TLS_ENABLED)/$(TLS_ENABLED)/g" $$TEMP_FILE ;\
kubectl -n $(NAMESPACE) apply -f $$TEMP_FILE ;\
kubectl patch -n $(NAMESPACE) authorino/$(AUTHORINO_INSTANCE) --type='merge' -p '{"spec":{"image": "$(AUTHORINO_IMAGE)"}}' ;\
rm -rf $$TEMP_FILE ;\
}


##@ Limitador

deploy-limitador:
kubectl create configmap limits --from-file=$(PROJECT_PATH)/utils/docker-compose/limits.yaml
kubectl -n $(NAMESPACE) apply -f $(PROJECT_PATH)/utils/deploy/limitador.yaml


##@ User Apps

.PHONY: user-apps


ifeq (true,$(TLS_ENABLED))
ENVOY_OVERLAY = tls
else
ENVOY_OVERLAY = notls
endif
user-apps: ## Deploys talker API and envoy
kubectl -n $(NAMESPACE) apply -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/talker-api/talker-api-deploy.yaml
kubectl -n $(NAMESPACE) apply -f $(PROJECT_PATH)/utils/deploy/envoy-$(ENVOY_OVERLAY).yaml
kubectl -n $(NAMESPACE) apply -f $(PROJECT_PATH)/utils/deploy/authconfig.yaml


##@ Util

.PHONY: local-setup local-env-setup local-cleanup local-rollout sed

local-setup: local-env-setup
kubectl -n $(NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all
@{ \
echo "Now you can export the envoy service by doing:"; \
echo "kubectl port-forward --namespace $(NAMESPACE) deployment/envoy 8000:8000"; \
echo "After that, you can curl -H \"Host: myhost.com\" localhost:8000"; \
}

local-env-setup:
$(MAKE) kind-delete-cluster
$(MAKE) kind-create-cluster
$(MAKE) install-authorino-operator
$(MAKE) deploy-authorino
$(MAKE) deploy-limitador
$(MAKE) user-apps

local-cleanup: kind ## Delete the "wasm-auth-local" kind cluster.
$(MAKE) kind-delete-cluster

local-rollout:
$(MAKE) user-apps
kubectl rollout restart -n $(NAMESPACE) deployment/envoy
kubectl -n $(NAMESPACE) wait --timeout=300s --for=condition=Available deployments --all

ifeq ($(shell uname),Darwin)
SED=$(shell which gsed)
else
SED=$(shell which sed)
endif
sed: ## Checks if GNU sed is installed
ifeq ($(SED),)
@echo "Cannot find GNU sed installed."
exit 1
endif
Loading

0 comments on commit d5a96f7

Please sign in to comment.