Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration development environment #67

Merged
merged 9 commits into from
Sep 18, 2024
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
116 changes: 106 additions & 10 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 @@ -33,6 +41,12 @@ rateLimitPolicies:
- static:
key: admin
value: "1"
actions:
- extension: ratelimit-ext
data:
static:
key: host
value: rlp-ns-A/rlp-name-A
```

## Features
Expand All @@ -57,6 +71,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 +110,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 +142,85 @@ 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
```

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
```

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 +232,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 +269,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 +283,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
28 changes: 28 additions & 0 deletions utils/deploy/authconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
name: talker-api-protection
spec:
hosts:
- effective-route-1
authentication:
"friends":
apiKey:
selector:
matchLabels:
group: friends
credentials:
authorizationHeader:
prefix: APIKEY
---
apiVersion: v1
kind: Secret
metadata:
name: api-key-1
labels:
authorino.kuadrant.io/managed-by: authorino
group: friends
stringData:
api_key: "ndyBzreUzF4zqDQsqSPMHkRhriEOtcRx"
type: Opaque
Loading
Loading