Compare commits
1 Commits
5eac425fda
...
a1b07c568e
Author | SHA1 | Date |
---|---|---|
Matthieu Lamalle | a1b07c568e |
9
Makefile
9
Makefile
|
@ -17,8 +17,7 @@ GOTEST_ARGS ?= -short
|
||||||
OPENWRT_DEVICE ?= 192.168.1.1
|
OPENWRT_DEVICE ?= 192.168.1.1
|
||||||
|
|
||||||
SIEGE_URLS_FILE ?= misc/siege/urls.txt
|
SIEGE_URLS_FILE ?= misc/siege/urls.txt
|
||||||
SIEGE_CONCURRENCY ?= 50
|
SIEGE_CONCURRENCY ?= 100
|
||||||
SIEGE_DURATION ?= 1M
|
|
||||||
|
|
||||||
data/bootstrap.d/dummy.yml:
|
data/bootstrap.d/dummy.yml:
|
||||||
mkdir -p data/bootstrap.d
|
mkdir -p data/bootstrap.d
|
||||||
|
@ -115,7 +114,7 @@ grafterm: tools/grafterm/bin/grafterm
|
||||||
siege:
|
siege:
|
||||||
$(eval TMP := $(shell mktemp))
|
$(eval TMP := $(shell mktemp))
|
||||||
cat $(SIEGE_URLS_FILE) | envsubst > $(TMP)
|
cat $(SIEGE_URLS_FILE) | envsubst > $(TMP)
|
||||||
siege -R ./misc/siege/siege.conf -i -b -c $(SIEGE_CONCURRENCY) -t $(SIEGE_DURATION) -f $(TMP)
|
siege -i -b -c $(SIEGE_CONCURRENCY) -f $(TMP)
|
||||||
rm -rf $(TMP)
|
rm -rf $(TMP)
|
||||||
|
|
||||||
tools/gitea-release/bin/gitea-release.sh:
|
tools/gitea-release/bin/gitea-release.sh:
|
||||||
|
@ -132,7 +131,7 @@ tools/grafterm/bin/grafterm:
|
||||||
GOBIN=$(PWD)/tools/grafterm/bin go install github.com/slok/grafterm/cmd/grafterm@v0.2.0
|
GOBIN=$(PWD)/tools/grafterm/bin go install github.com/slok/grafterm/cmd/grafterm@v0.2.0
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
go test -bench=. -run '^$$' -benchtime=10s ./internal/bench
|
go test -bench=. -run '^$$' ./internal/bench
|
||||||
|
|
||||||
tools/benchstat/bin/benchstat:
|
tools/benchstat/bin/benchstat:
|
||||||
mkdir -p tools/benchstat/bin
|
mkdir -p tools/benchstat/bin
|
||||||
|
@ -151,7 +150,7 @@ run-redis:
|
||||||
-v $(PWD)/data/redis:/data \
|
-v $(PWD)/data/redis:/data \
|
||||||
-p 6379:6379 \
|
-p 6379:6379 \
|
||||||
redis:alpine3.17 \
|
redis:alpine3.17 \
|
||||||
redis-server --save 60 1 --loglevel debug
|
redis-server --save 60 1 --loglevel warning
|
||||||
|
|
||||||
redis-shell:
|
redis-shell:
|
||||||
docker exec -it \
|
docker exec -it \
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
- [(FR) - Ajouter une authentification OpenID Connect](./fr/tutorials/add-oidc-authn-layer.md)
|
- [(FR) - Ajouter une authentification OpenID Connect](./fr/tutorials/add-oidc-authn-layer.md)
|
||||||
- [(FR) - Amorçage d'un serveur Bouncer via la configuration](./fr/tutorials/bootstrapping.md)
|
- [(FR) - Amorçage d'un serveur Bouncer via la configuration](./fr/tutorials/bootstrapping.md)
|
||||||
- [(FR) - Intégration avec Kubernetes](./fr/tutorials/kubernetes-integration.md)
|
- [(FR) - Intégration avec Kubernetes](./fr/tutorials/kubernetes-integration.md)
|
||||||
- [(FR) - Profilage](./fr/tutorials/profiling.md)
|
|
||||||
|
|
||||||
### Développement
|
### Développement
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ Bouncer utilise le projet [`expr`](https://expr-lang.org/) comme DSL. En plus de
|
||||||
Le comportement des règles par défaut est le suivant:
|
Le comportement des règles par défaut est le suivant:
|
||||||
|
|
||||||
1. L'ensemble des entêtes HTTP correspondant au patron `Remote-*` sont supprimés ;
|
1. L'ensemble des entêtes HTTP correspondant au patron `Remote-*` sont supprimés ;
|
||||||
2. L'identifiant de l'utilisateur identifié (`vars.user.subject`) est exporté sous la forme de l'entête HTTP `Remote-User` ;
|
2. L'identifiant de l'utilisateur identifié (`user.subject`) est exporté sous la forme de l'entête HTTP `Remote-User` ;
|
||||||
3. L'ensemble des attributs de l'utilisateur identifié (`vars.user.attrs`) sont exportés sous la forme `Remote-User-Attr-<name>` où `<name>` est le nom de l'attribut en minuscule, avec les `_` transformés en `-`.
|
3. L'ensemble des attributs de l'utilisateur identifié (`user.attrs`) sont exportés sous la forme `Remote-User-Attr-<name>` où `<name>` est le nom de l'attribut en minuscule, avec les `_` transformés en `-`.
|
||||||
|
|
||||||
### Fonctions
|
### Fonctions
|
||||||
|
|
||||||
|
@ -36,25 +36,25 @@ Le comportement des règles par défaut est le suivant:
|
||||||
|
|
||||||
Interdire l'accès à l'utilisateur.
|
Interdire l'accès à l'utilisateur.
|
||||||
|
|
||||||
##### `add_header(ctx, name string, value string)`
|
##### `add_header(name string, value string)`
|
||||||
|
|
||||||
Ajouter une valeur à un entête HTTP via son nom `name` et sa valeur `value`.
|
Ajouter une valeur à un entête HTTP via son nom `name` et sa valeur `value`.
|
||||||
|
|
||||||
##### `set_header(ctx, name string, value string)`
|
##### `set_header(name string, value string)`
|
||||||
|
|
||||||
Définir la valeur d'un entête HTTP via son nom `name` et sa valeur `value`. La valeur précédente est écrasée.
|
Définir la valeur d'un entête HTTP via son nom `name` et sa valeur `value`. La valeur précédente est écrasée.
|
||||||
|
|
||||||
##### `del_headers(ctx, pattern string)`
|
##### `del_headers(pattern string)`
|
||||||
|
|
||||||
Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `pattern`.
|
Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `pattern`.
|
||||||
|
|
||||||
Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`, signifiant un ou plusieurs caractères arbitraires.
|
Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`, signifiant un ou plusieurs caractères arbitraires.
|
||||||
|
|
||||||
##### `set_host(ctx, host string)`
|
##### `set_host(host string)`
|
||||||
|
|
||||||
Modifier la valeur de l'entête `Host` de la requête.
|
Modifier la valeur de l'entête `Host` de la requête.
|
||||||
|
|
||||||
##### `set_url(ctx, url string)`
|
##### `set_url(url string)`
|
||||||
|
|
||||||
Modifier l'URL du serveur cible.
|
Modifier l'URL du serveur cible.
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ Modifier l'URL du serveur cible.
|
||||||
|
|
||||||
Les règles ont accès aux variables suivantes pendant leur exécution.
|
Les règles ont accès aux variables suivantes pendant leur exécution.
|
||||||
|
|
||||||
#### `vars.user`
|
#### `user`
|
||||||
|
|
||||||
L'utilisateur identifié par le layer.
|
L'utilisateur identifié par le layer.
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ Les options disponibles pour le layer sont décrites via un [schéma JSON](https
|
||||||
|
|
||||||
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
||||||
|
|
||||||
## Objet `vars.user` et attributs
|
## Objet `user` et attributs
|
||||||
|
|
||||||
L'objet `user` exposé au moteur de règles sera construit de la manière suivante:
|
L'objet `user` exposé au moteur de règles sera construit de la manière suivante:
|
||||||
|
|
||||||
- `vars.user.subject` sera initialisé avec le nom d'utilisateur identifié ;
|
- `user.subject` sera initialisé avec le nom d'utilisateur identifié ;
|
||||||
- `vars.user.attrs` sera composé des attributs associés à l'utilisation (voir les options).
|
- `user.attrs` sera composé des attributs associés à l'utilisation (voir les options).
|
||||||
|
|
||||||
## Métriques
|
## Métriques
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ Les options disponibles pour le layer sont décrites via un [schéma JSON](https
|
||||||
|
|
||||||
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
||||||
|
|
||||||
## Objet `vars.user` et attributs
|
## Objet `user` et attributs
|
||||||
|
|
||||||
L'objet `vars.user` exposé au moteur de règles sera construit de la manière suivante:
|
L'objet `user` exposé au moteur de règles sera construit de la manière suivante:
|
||||||
|
|
||||||
- `vars.user.subject` sera initialisé avec le couple `<remote_address>:<remote_port>` ;
|
- `user.subject` sera initialisé avec le couple `<remote_address>:<remote_port>` ;
|
||||||
- `vars.user.attrs` sera vide.
|
- `user.attrs` sera vide.
|
||||||
|
|
||||||
## Métriques
|
## Métriques
|
||||||
|
|
||||||
|
|
|
@ -16,18 +16,18 @@ Les options disponibles pour le layer sont décrites via un [schéma JSON](https
|
||||||
|
|
||||||
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
En plus de ces options spécifiques le layer peut également être configuré via [les options communes aux layers `authn-*`](../../../../../internal/proxy/director/layer/authn/layer-options.json).
|
||||||
|
|
||||||
## Objet `vars.user` et attributs
|
## Objet `user` et attributs
|
||||||
|
|
||||||
L'objet `vars.user` exposé au moteur de règles sera construit de la manière suivante:
|
L'objet `user` exposé au moteur de règles sera construit de la manière suivante:
|
||||||
|
|
||||||
- `vars.user.subject` sera initialisé avec la valeur du [claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) `sub` extrait de l'`idToken` récupéré lors de l'authentification ;
|
- `user.subject` sera initialisé avec la valeur du [claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) `sub` extrait de l'`idToken` récupéré lors de l'authentification ;
|
||||||
- `vars.user.attrs` comportera les propriétés suivantes:
|
- `user.attrs` comportera les propriétés suivantes:
|
||||||
|
|
||||||
- L'ensemble des `claims` provenant de l'`idToken` seront transposés en `claim_<name>` (ex: `idToken.iss` sera transposé en `vars.user.attrs.claim_iss`) ;
|
- L'ensemble des `claims` provenant de l'`idToken` seront transposés en `claim_<name>` (ex: `idToken.iss` sera transposé en `user.attrs.claim_iss`) ;
|
||||||
- `vars.user.attrs.access_token`: le jeton d'accès associé à l'authentification ;
|
- `user.attrs.access_token`: le jeton d'accès associé à l'authentification ;
|
||||||
- `vars.user.attrs.refresh_token`: le jeton de rafraîchissement associé à l'authentification (si disponible, en fonction des `scopes` demandés par le client) ;
|
- `user.attrs.refresh_token`: le jeton de rafraîchissement associé à l'authentification (si disponible, en fonction des `scopes` demandés par le client) ;
|
||||||
- `vars.user.attrs.token_expiry`: Horodatage Unix (en secondes) associé à la date d'expiration du jeton d'accès ;
|
- `user.attrs.token_expiry`: Horodatage Unix (en secondes) associé à la date d'expiration du jeton d'accès ;
|
||||||
- `vars.user.attrs.logout_url`: URL de déconnexion pour la suppression de la session Bouncer.
|
- `user.attrs.logout_url`: URL de déconnexion pour la suppression de la session Bouncer.
|
||||||
|
|
||||||
**Attention** Cette URL ne permet dans la plupart des cas que de supprimer la session côté Bouncer. La suppression de la session côté fournisseur d'identité est conditionné à la présence ou non de l'attribut [`end_session_endpoint`](https://openid.net/specs/openid-connect-session-1_0-17.html#OPMetadata) dans les données du point d'entrée de découverte de service (`.wellknown/openid-configuration`).
|
**Attention** Cette URL ne permet dans la plupart des cas que de supprimer la session côté Bouncer. La suppression de la session côté fournisseur d'identité est conditionné à la présence ou non de l'attribut [`end_session_endpoint`](https://openid.net/specs/openid-connect-session-1_0-17.html#OPMetadata) dans les données du point d'entrée de découverte de service (`.wellknown/openid-configuration`).
|
||||||
|
|
||||||
|
|
|
@ -24,15 +24,15 @@ Bouncer utilise le projet [`expr`](https://expr-lang.org/) comme DSL. En plus de
|
||||||
|
|
||||||
#### Communes
|
#### Communes
|
||||||
|
|
||||||
##### `add_header(ctx, name string, value string)`
|
##### `add_header(name string, value string)`
|
||||||
|
|
||||||
Ajouter une valeur à un entête HTTP via son nom `name` et sa valeur `value`.
|
Ajouter une valeur à un entête HTTP via son nom `name` et sa valeur `value`.
|
||||||
|
|
||||||
##### `set_header(ctx, name string, value string)`
|
##### `set_header(name string, value string)`
|
||||||
|
|
||||||
Définir la valeur d'un entête HTTP via son nom `name` et sa valeur `value`. La valeur précédente est écrasée.
|
Définir la valeur d'un entête HTTP via son nom `name` et sa valeur `value`. La valeur précédente est écrasée.
|
||||||
|
|
||||||
##### `del_headers(ctx, pattern string)`
|
##### `del_headers(pattern string)`
|
||||||
|
|
||||||
Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `pattern`.
|
Supprimer un ou plusieurs entêtes HTTP dont le nom correspond au patron `pattern`.
|
||||||
|
|
||||||
|
@ -40,11 +40,11 @@ Le patron est défini par une chaîne comprenant un ou plusieurs caractères `*`
|
||||||
|
|
||||||
#### Requête
|
#### Requête
|
||||||
|
|
||||||
##### `set_host(ctx, host string)`
|
##### `set_host(host string)`
|
||||||
|
|
||||||
Modifier la valeur de l'entête `Host` de la requête.
|
Modifier la valeur de l'entête `Host` de la requête.
|
||||||
|
|
||||||
##### `set_url(ctx, url string)`
|
##### `set_url(url string)`
|
||||||
|
|
||||||
Modifier l'URL du serveur cible.
|
Modifier l'URL du serveur cible.
|
||||||
|
|
||||||
|
@ -58,28 +58,7 @@ Les règles ont accès aux variables suivantes pendant leur exécution. **Ces do
|
||||||
|
|
||||||
#### Requête
|
#### Requête
|
||||||
|
|
||||||
##### `vars.original_url`
|
##### `request`
|
||||||
|
|
||||||
L'URL originale, avant réécriture du `Host` par Bouncer.
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
scheme: "string", // Schéma HTTP de l'URL
|
|
||||||
opaque: "string", // Données opaque de l'URL
|
|
||||||
user: { // Identifiants d'URL (Basic Auth)
|
|
||||||
username: "",
|
|
||||||
password: ""
|
|
||||||
},
|
|
||||||
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
|
||||||
path: "string", // Chemin de l'URL (format assaini)
|
|
||||||
rawPath: "string", // Chemin de l'URL (format brut)
|
|
||||||
raw_query: "string", // Variables d'URL (format brut)
|
|
||||||
fragment : "string", // Fragment d'URL (format assaini)
|
|
||||||
raw_fragment : "string" // Fragment d'URL (format brut)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `vars.request`
|
|
||||||
|
|
||||||
La requête en cours de traitement.
|
La requête en cours de traitement.
|
||||||
|
|
||||||
|
@ -88,65 +67,61 @@ La requête en cours de traitement.
|
||||||
method: "string", // Méthode HTTP
|
method: "string", // Méthode HTTP
|
||||||
host: "string", // Nom d'hôte (`Host`) associé à la requête
|
host: "string", // Nom d'hôte (`Host`) associé à la requête
|
||||||
url: { // URL associée à la requête sous sa forme structurée
|
url: { // URL associée à la requête sous sa forme structurée
|
||||||
scheme: "string", // Schéma HTTP de l'URL
|
"scheme": "string", // Schéma HTTP de l'URL
|
||||||
opaque: "string", // Données opaque de l'URL
|
"opaque": "string", // Données opaque de l'URL
|
||||||
user: { // Identifiants d'URL (Basic Auth)
|
"user": { // Identifiants d'URL (Basic Auth)
|
||||||
username: "",
|
"username": "",
|
||||||
password: ""
|
"password": ""
|
||||||
},
|
},
|
||||||
host: "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
"host": "string", // Nom d'hôte (<domaine>:<port>) de l'URL
|
||||||
path: "string", // Chemin de l'URL (format assaini)
|
"path": "string", // Chemin de l'URL (format assaini)
|
||||||
rawPath: "string", // Chemin de l'URL (format brut)
|
"rawPath": "string", // Chemin de l'URL (format brut)
|
||||||
raw_query: "string", // Variables d'URL (format brut)
|
"rawQuery": "string", // Variables d'URL (format brut)
|
||||||
fragment : "string", // Fragment d'URL (format assaini)
|
"fragment" : "string", // Fragment d'URL (format assaini)
|
||||||
raw_fragment : "string" // Fragment d'URL (format brut)
|
"rawFragment" : "string" // Fragment d'URL (format brut)
|
||||||
},
|
},
|
||||||
raw_url: "string", // URL associée à la requête (format assaini)
|
rawUrl: "string", // URL associée à la requête (format assaini)
|
||||||
proto: "string", // Numéro de version du protocole utilisé
|
proto: "string", // Numéro de version du protocole utilisé
|
||||||
proto_major: "int", // Numéro de version majeure du protocole utilisé
|
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
||||||
proto_minor: "int", // Numéro de version mineur du protocole utilisé
|
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
||||||
header: { // Table associative des entêtes HTTP associés à la requête
|
header: { // Table associative des entêtes HTTP associés à la requête
|
||||||
"string": ["string"]
|
"string": ["string"]
|
||||||
},
|
},
|
||||||
content_length: "int", // Taille du corps de la requête
|
contentLength: "int", // Taille du corps de la requête
|
||||||
transfer_encoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
transferEncoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
||||||
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
||||||
"string": ["string"]
|
"string": ["string"]
|
||||||
},
|
},
|
||||||
remote_addr: "string", // Adresse du client HTTP à l'origine de la requête
|
remoteAddr: "string", // Adresse du client HTTP à l'origine de la requête
|
||||||
request_uri: "string" // URL "brute" associée à la requêtes (avant opérations d'assainissement, utiliser "url" plutôt)
|
requestUri: "string" // URL "brute" associée à la requêtes (avant opérations d'assainissement, utiliser "url" plutôt)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Réponse
|
#### Réponse
|
||||||
|
|
||||||
##### `vars.response`
|
##### `response`
|
||||||
|
|
||||||
La réponse en cours de traitement.
|
La réponse en cours de traitement.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
status_code: "int", // Code de statut de la réponse
|
statusCode: "int", // Code de statut de la réponse
|
||||||
status: "string", // Message associé au code de statut
|
status: "string", // Message associé au code de statut
|
||||||
proto: "string", // Numéro de version du protocole utilisé
|
proto: "string", // Numéro de version du protocole utilisé
|
||||||
proto_major: "int", // Numéro de version majeure du protocole utilisé
|
protoMajor: "int", // Numéro de version majeure du protocole utilisé
|
||||||
proto_minor: "int", // Numéro de version mineur du protocole utilisé
|
protoMinor: "int", // Numéro de version mineur du protocole utilisé
|
||||||
header: { // Table associative des entêtes HTTP associés à la requête
|
header: { // Table associative des entêtes HTTP associés à la requête
|
||||||
"string": ["string"]
|
"string": ["string"]
|
||||||
},
|
},
|
||||||
content_length: "int", // Taille du corps de la réponse
|
contentLength: "int", // Taille du corps de la réponse
|
||||||
transfer_encoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
transferEncoding: ["string"], // MIME-Type(s) d'encodage du corps de la requête
|
||||||
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
trailer: { // Table associative des entêtes HTTP associés à la requête, transmises après le corps de la requête
|
||||||
"string": ["string"]
|
"string": ["string"]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
##### `vars.request`
|
##### `request`
|
||||||
|
|
||||||
_Voir section précédente._
|
|
||||||
|
|
||||||
##### `vars.original_url`
|
|
||||||
|
|
||||||
_Voir section précédente._
|
_Voir section précédente._
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,10 @@
|
||||||
# Étudier les performances de Bouncer
|
# Étudier les performances de Bouncer
|
||||||
|
|
||||||
## In situ
|
|
||||||
|
|
||||||
Il est possible d'activer via la configuration de Bouncer de endpoints capable de générer des fichiers de profil au format [`pprof`](https://github.com/google/pprof). Par défaut, le point d'entrée est `.bouncer/profiling` (l'activation et la personnalisation de ce point d'entrée sont modifiables via la [configuration](../../../misc/packaging/common/config.yml)).
|
|
||||||
|
|
||||||
**Exemple:** Visualiser l'utilisation mémoire de Bouncer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go tool pprof -web http://<bouncer_proxy>/.bouncer/profiling/heap
|
|
||||||
```
|
|
||||||
|
|
||||||
L'ensemble des profils disponibles sont visibles à l'adresse `http://<bouncer_proxy>/.bouncer/profiling`.
|
|
||||||
|
|
||||||
## En développement
|
|
||||||
|
|
||||||
Le package `./internal` est dédié à l'étude des performances de Bouncer. Il contient une suite de benchmarks simulant de proxies avec différentes configurations de layers afin d'évaluer les points d'engorgement sur le traitement des requêtes.
|
Le package `./internal` est dédié à l'étude des performances de Bouncer. Il contient une suite de benchmarks simulant de proxies avec différentes configurations de layers afin d'évaluer les points d'engorgement sur le traitement des requêtes.
|
||||||
|
|
||||||
Voir le répertoire `./internal/bench/testdata/proxies` pour voir les différentes configurations de cas.
|
Voir le répertoire `./internal/bench/testdata/proxies` pour voir les différentes configurations de cas.
|
||||||
|
|
||||||
### Lancer les benchmarks
|
## Lancer les benchmarks
|
||||||
|
|
||||||
Le plus simple est d'utiliser la commande `make bench` qui exécutera séquentiellement tous les benchmarks. Il est également possible de lancer un benchmark spécifique via la commande suivante:
|
Le plus simple est d'utiliser la commande `make bench` qui exécutera séquentiellement tous les benchmarks. Il est également possible de lancer un benchmark spécifique via la commande suivante:
|
||||||
|
|
||||||
|
@ -33,7 +19,7 @@ Par exemple:
|
||||||
go test -bench='BenchmarkProxies/basic-auth' -run='^$' ./internal/bench
|
go test -bench='BenchmarkProxies/basic-auth' -run='^$' ./internal/bench
|
||||||
```
|
```
|
||||||
|
|
||||||
### Visualiser les profils d'exécution
|
## Visualiser les profils d'exécution
|
||||||
|
|
||||||
Vous pouvez visualiser les profils d'exécution via la commande suivante:
|
Vous pouvez visualiser les profils d'exécution via la commande suivante:
|
||||||
|
|
||||||
|
@ -49,7 +35,7 @@ Par exemple:
|
||||||
go tool pprof -web ./internal/bench/testdata/proxies/basic-auth.prof
|
go tool pprof -web ./internal/bench/testdata/proxies/basic-auth.prof
|
||||||
```
|
```
|
||||||
|
|
||||||
### Comparer les évolutions
|
## Comparer les évolutions
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Lancer un premier benchmark
|
# Lancer un premier benchmark
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (s *Server) initRepositories(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) initRedisClient(ctx context.Context) error {
|
func (s *Server) initRedisClient(ctx context.Context) error {
|
||||||
client := setup.NewSharedClient(s.redisConfig)
|
client := setup.NewRedisClient(ctx, s.redisConfig)
|
||||||
|
|
||||||
s.redisClient = client
|
s.redisClient = client
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/pprof"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||||
|
@ -156,34 +155,6 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.serverConfig.Profiling.Enabled {
|
|
||||||
profiling := s.serverConfig.Profiling
|
|
||||||
logger.Info(ctx, "enabling profiling", logger.F("endpoint", profiling.Endpoint))
|
|
||||||
|
|
||||||
router.Group(func(r chi.Router) {
|
|
||||||
if profiling.BasicAuth != nil {
|
|
||||||
logger.Info(ctx, "enabling authentication on metrics endpoint")
|
|
||||||
|
|
||||||
r.Use(middleware.BasicAuth(
|
|
||||||
"profiling",
|
|
||||||
profiling.BasicAuth.CredentialsMap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Route(string(profiling.Endpoint), func(r chi.Router) {
|
|
||||||
r.HandleFunc("/", pprof.Index)
|
|
||||||
r.HandleFunc("/cmdline", pprof.Cmdline)
|
|
||||||
r.HandleFunc("/profile", pprof.Profile)
|
|
||||||
r.HandleFunc("/symbol", pprof.Symbol)
|
|
||||||
r.HandleFunc("/trace", pprof.Trace)
|
|
||||||
r.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := chi.URLParam(r, "name")
|
|
||||||
pprof.Handler(name).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Route("/api/v1", func(r chi.Router) {
|
router.Route("/api/v1", func(r chi.Router) {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(auth.Middleware(
|
r.Use(auth.Middleware(
|
||||||
|
|
|
@ -3,6 +3,7 @@ package proxy_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
@ -23,7 +24,6 @@ import (
|
||||||
redisStore "forge.cadoles.com/cadoles/bouncer/internal/store/redis"
|
redisStore "forge.cadoles.com/cadoles/bouncer/internal/store/redis"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||||
|
@ -39,19 +39,6 @@ func BenchmarkProxies(b *testing.B) {
|
||||||
name := strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))
|
name := strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))
|
||||||
|
|
||||||
b.Run(name, func(b *testing.B) {
|
b.Run(name, func(b *testing.B) {
|
||||||
heap, err := os.Create(filepath.Join("testdata", "proxies", name+"_heap.prof"))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("%+v", errors.Wrapf(err, "could not create heap profile"))
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
defer heap.Close()
|
|
||||||
|
|
||||||
if err := pprof.WriteHeapProfile(heap); err != nil {
|
|
||||||
b.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
conf, err := loadProxyBenchConfig(f)
|
conf, err := loadProxyBenchConfig(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("%+v", errors.Wrapf(err, "could notre load bench config"))
|
b.Fatalf("%+v", errors.Wrapf(err, "could notre load bench config"))
|
||||||
|
@ -91,7 +78,7 @@ func BenchmarkProxies(b *testing.B) {
|
||||||
|
|
||||||
b.Logf("fetching url '%s'", rawProxyURL)
|
b.Logf("fetching url '%s'", rawProxyURL)
|
||||||
|
|
||||||
profile, err := os.Create(filepath.Join("testdata", "proxies", name+"_cpu.prof"))
|
profile, err := os.Create(filepath.Join("testdata", "proxies", name+".prof"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("%+v", errors.Wrapf(err, "could not create cpu profile"))
|
b.Fatalf("%+v", errors.Wrapf(err, "could not create cpu profile"))
|
||||||
}
|
}
|
||||||
|
@ -99,7 +86,7 @@ func BenchmarkProxies(b *testing.B) {
|
||||||
defer profile.Close()
|
defer profile.Close()
|
||||||
|
|
||||||
if err := pprof.StartCPUProfile(profile); err != nil {
|
if err := pprof.StartCPUProfile(profile); err != nil {
|
||||||
b.Fatalf("%+v", errors.WithStack(err))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
|
@ -240,12 +227,7 @@ func createProxy(name string, conf *proxyBenchConfig, logf func(format string, a
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
appConf := config.NewDefault()
|
layers, err := setup.GetLayers(context.Background(), config.NewDefault())
|
||||||
appConf.Logger.Level = config.InterpolatedInt(logger.LevelError)
|
|
||||||
appConf.Layers.Authn.TemplateDir = "../../layers/authn/templates"
|
|
||||||
appConf.Layers.Queue.TemplateDir = "../../layers/queue/templates"
|
|
||||||
|
|
||||||
layers, err := setup.GetLayers(context.Background(), appConf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithStack(err)
|
return nil, nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ proxy:
|
||||||
attributes:
|
attributes:
|
||||||
email: foo@bar.com
|
email: foo@bar.com
|
||||||
rules:
|
rules:
|
||||||
- set_header(ctx, "Remote-User-Attr-Email", vars.user.attrs.email)
|
- set_header("Remote-User-Attr-Email", user.attrs.email)
|
||||||
fetch:
|
fetch:
|
||||||
url:
|
url:
|
||||||
user:
|
user:
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
proxy:
|
|
||||||
from: ["*"]
|
|
||||||
to: ""
|
|
||||||
layers:
|
|
||||||
queue:
|
|
||||||
type: queue
|
|
||||||
enabled: true
|
|
||||||
options:
|
|
||||||
capacity: 100
|
|
||||||
keepAlive: 10s
|
|
|
@ -8,5 +8,5 @@ proxy:
|
||||||
options:
|
options:
|
||||||
rules:
|
rules:
|
||||||
request:
|
request:
|
||||||
- set_host(ctx, vars.request.url.host)
|
- set_host(request.url.host)
|
||||||
- set_header(ctx, "X-Proxied-With", "bouncer")
|
- set_header("X-Proxied-With", "bouncer")
|
||||||
|
|
|
@ -35,16 +35,13 @@ func RunCommand() *cli.Command {
|
||||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||||
|
|
||||||
projectVersion := ctx.String("projectVersion")
|
projectVersion := ctx.String("projectVersion")
|
||||||
|
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Admin.Sentry, projectVersion)
|
||||||
if conf.Proxy.Sentry.DSN != "" {
|
if err != nil {
|
||||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Proxy.Sentry, projectVersion)
|
return errors.Wrap(err, "could not initialize sentry client")
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not initialize sentry client")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer flushSentry()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer flushSentry()
|
||||||
|
|
||||||
integrations, err := setup.SetupIntegrations(ctx.Context, conf)
|
integrations, err := setup.SetupIntegrations(ctx.Context, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not setup integrations")
|
return errors.Wrap(err, "could not setup integrations")
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
<h2>Incoming headers</h2>
|
<h2>Incoming headers</h2>
|
||||||
<table style="width: 100%">
|
<table style="width: 100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: left">
|
<tr>
|
||||||
<th>Key</th>
|
<th>Key</th>
|
||||||
<th>Value</th>
|
<th>Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range $key, $val := .Request.Header }}
|
{{ range $key, $val := .Request.Header }}
|
||||||
<tr style="text-align: left">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<b>{{ $key }}</b>
|
<b>{{ $key }}</b>
|
||||||
</td>
|
</td>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<h2>Incoming cookies</h2>
|
<h2>Incoming cookies</h2>
|
||||||
<table style="width: 100%">
|
<table style="width: 100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style="text-align: left">
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Domain</th>
|
<th>Domain</th>
|
||||||
<th>Path</th>
|
<th>Path</th>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range $cookie := .Request.Cookies }}
|
{{ range $cookie := .Request.Cookies }}
|
||||||
<tr style="text-align: left">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<b>{{ $cookie.Name }}</b>
|
<b>{{ $cookie.Name }}</b>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -30,16 +30,13 @@ func RunCommand() *cli.Command {
|
||||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||||
|
|
||||||
projectVersion := ctx.String("projectVersion")
|
projectVersion := ctx.String("projectVersion")
|
||||||
|
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Proxy.Sentry, projectVersion)
|
||||||
if conf.Proxy.Sentry.DSN != "" {
|
if err != nil {
|
||||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Proxy.Sentry, projectVersion)
|
return errors.Wrap(err, "could not initialize sentry client")
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not initialize sentry client")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer flushSentry()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer flushSentry()
|
||||||
|
|
||||||
layers, err := setup.GetLayers(ctx.Context, conf)
|
layers, err := setup.GetLayers(ctx.Context, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not initialize director layers")
|
return errors.Wrap(err, "could not initialize director layers")
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
type AdminServerConfig struct {
|
type AdminServerConfig struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
CORS CORSConfig `yaml:"cors"`
|
CORS CORSConfig `yaml:"cors"`
|
||||||
Auth AuthConfig `yaml:"auth"`
|
Auth AuthConfig `yaml:"auth"`
|
||||||
Metrics MetricsConfig `yaml:"metrics"`
|
Metrics MetricsConfig `yaml:"metrics"`
|
||||||
Profiling ProfilingConfig `yaml:"profiling"`
|
Sentry SentryConfig `yaml:"sentry"`
|
||||||
Sentry SentryConfig `yaml:"sentry"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultAdminServerConfig() AdminServerConfig {
|
func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||||
return AdminServerConfig{
|
return AdminServerConfig{
|
||||||
HTTP: NewHTTPConfig("127.0.0.1", 8081),
|
HTTP: NewHTTPConfig("127.0.0.1", 8081),
|
||||||
CORS: NewDefaultCORSConfig(),
|
CORS: NewDefaultCORSConfig(),
|
||||||
Auth: NewDefaultAuthConfig(),
|
Auth: NewDefaultAuthConfig(),
|
||||||
Metrics: NewDefaultMetricsConfig(),
|
Metrics: NewDefaultMetricsConfig(),
|
||||||
Sentry: NewDefaultSentryConfig(),
|
Sentry: NewDefaultSentryConfig(),
|
||||||
Profiling: NewDefaultProfilingConfig(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package config
|
|
||||||
|
|
||||||
type ProfilingConfig struct {
|
|
||||||
Enabled InterpolatedBool `yaml:"enabled"`
|
|
||||||
Endpoint InterpolatedString `yaml:"endpoint"`
|
|
||||||
BasicAuth *BasicAuthConfig `yaml:"basicAuth"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultProfilingConfig() ProfilingConfig {
|
|
||||||
return ProfilingConfig{
|
|
||||||
Enabled: true,
|
|
||||||
Endpoint: "/.bouncer/profiling",
|
|
||||||
BasicAuth: nil,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,6 @@ type ProxyServerConfig struct {
|
||||||
Debug InterpolatedBool `yaml:"debug"`
|
Debug InterpolatedBool `yaml:"debug"`
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
Metrics MetricsConfig `yaml:"metrics"`
|
Metrics MetricsConfig `yaml:"metrics"`
|
||||||
Profiling ProfilingConfig `yaml:"profiling"`
|
|
||||||
Transport TransportConfig `yaml:"transport"`
|
Transport TransportConfig `yaml:"transport"`
|
||||||
Dial DialConfig `yaml:"dial"`
|
Dial DialConfig `yaml:"dial"`
|
||||||
Sentry SentryConfig `yaml:"sentry"`
|
Sentry SentryConfig `yaml:"sentry"`
|
||||||
|
@ -28,7 +27,6 @@ func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||||
Sentry: NewDefaultSentryConfig(),
|
Sentry: NewDefaultSentryConfig(),
|
||||||
Cache: NewDefaultCacheConfig(),
|
Cache: NewDefaultCacheConfig(),
|
||||||
Templates: NewDefaultTemplatesConfig(),
|
Templates: NewDefaultTemplatesConfig(),
|
||||||
Profiling: NewDefaultProfilingConfig(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,6 @@ type RedisConfig struct {
|
||||||
WriteTimeout InterpolatedDuration `yaml:"writeTimeout"`
|
WriteTimeout InterpolatedDuration `yaml:"writeTimeout"`
|
||||||
DialTimeout InterpolatedDuration `yaml:"dialTimeout"`
|
DialTimeout InterpolatedDuration `yaml:"dialTimeout"`
|
||||||
LockMaxRetries InterpolatedInt `yaml:"lockMaxRetries"`
|
LockMaxRetries InterpolatedInt `yaml:"lockMaxRetries"`
|
||||||
MaxRetries InterpolatedInt `yaml:"maxRetries"`
|
|
||||||
PingInterval InterpolatedDuration `yaml:"pingInterval"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultRedisConfig() RedisConfig {
|
func NewDefaultRedisConfig() RedisConfig {
|
||||||
|
@ -27,7 +25,5 @@ func NewDefaultRedisConfig() RedisConfig {
|
||||||
WriteTimeout: InterpolatedDuration(30 * time.Second),
|
WriteTimeout: InterpolatedDuration(30 * time.Second),
|
||||||
DialTimeout: InterpolatedDuration(30 * time.Second),
|
DialTimeout: InterpolatedDuration(30 * time.Second),
|
||||||
LockMaxRetries: 10,
|
LockMaxRetries: 10,
|
||||||
MaxRetries: 3,
|
|
||||||
PingInterval: InterpolatedDuration(30 * time.Second),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,10 @@ func NewDefaultSentryConfig() SentryConfig {
|
||||||
Debug: false,
|
Debug: false,
|
||||||
FlushTimeout: NewInterpolatedDuration(2 * time.Second),
|
FlushTimeout: NewInterpolatedDuration(2 * time.Second),
|
||||||
AttachStacktrace: true,
|
AttachStacktrace: true,
|
||||||
SampleRate: 0.2,
|
SampleRate: 1,
|
||||||
EnableTracing: true,
|
EnableTracing: true,
|
||||||
TracesSampleRate: 0.2,
|
TracesSampleRate: 0.2,
|
||||||
ProfilesSampleRate: 0.2,
|
ProfilesSampleRate: 1,
|
||||||
IgnoreErrors: []string{},
|
IgnoreErrors: []string{},
|
||||||
SendDefaultPII: false,
|
SendDefaultPII: false,
|
||||||
ServerName: "",
|
ServerName: "",
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.applyRules(ctx, r, options, user); err != nil {
|
if err := l.applyRules(r, options, user); err != nil {
|
||||||
if errors.Is(err, ErrForbidden) {
|
if errors.Is(err, ErrForbidden) {
|
||||||
l.renderForbiddenPage(w, r, layer, options, user)
|
l.renderForbiddenPage(w, r, layer, options, user)
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package authn
|
package authn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||||
|
@ -10,32 +9,30 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Vars struct {
|
type Env struct {
|
||||||
User *User `expr:"user"`
|
User *User `expr:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) applyRules(ctx context.Context, r *http.Request, options *LayerOptions, user *User) error {
|
func (l *Layer) applyRules(r *http.Request, options *LayerOptions, user *User) error {
|
||||||
rules := options.Rules
|
rules := options.Rules
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := rule.NewEngine[*Vars](
|
engine, err := rule.NewEngine[*Env](
|
||||||
rule.WithRules(options.Rules...),
|
rule.WithRules(options.Rules...),
|
||||||
rule.WithExpr(getAuthnAPI()...),
|
rule.WithExpr(getAuthnAPI()...),
|
||||||
ruleHTTP.WithRequestFuncs(),
|
ruleHTTP.WithRequestFuncs(r),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := &Vars{
|
env := &Env{
|
||||||
User: user,
|
User: user,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ruleHTTP.WithRequest(ctx, r)
|
if _, err := engine.Apply(env); err != nil {
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer q.updateMetrics(layer.Proxy, layer.Name, options)
|
defer q.updateMetrics(ctx, layer.Proxy, layer.Name, options)
|
||||||
|
|
||||||
cookieName := q.getCookieName(layer.Name)
|
cookieName := q.getCookieName(layer.Name)
|
||||||
|
|
||||||
|
@ -217,9 +217,7 @@ func (q *Queue) refreshQueue(ctx context.Context, layerName store.LayerName, kee
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) updateMetrics(proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) {
|
func (q *Queue) updateMetrics(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Update queue capacity metric
|
// Update queue capacity metric
|
||||||
metricQueueCapacity.With(
|
metricQueueCapacity.With(
|
||||||
prometheus.Labels{
|
prometheus.Labels{
|
||||||
|
|
|
@ -6,9 +6,6 @@ import (
|
||||||
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/util"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
@ -16,10 +13,7 @@ import (
|
||||||
|
|
||||||
const LayerType store.LayerType = "rewriter"
|
const LayerType store.LayerType = "rewriter"
|
||||||
|
|
||||||
type Layer struct {
|
type Layer struct{}
|
||||||
requestRuleEngine *util.RevisionedRuleEngine[*RequestVars, *LayerOptions]
|
|
||||||
responseRuleEngine *util.RevisionedRuleEngine[*ResponseVars, *LayerOptions]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Layer) LayerType() store.LayerType {
|
func (l *Layer) LayerType() store.LayerType {
|
||||||
return LayerType
|
return LayerType
|
||||||
|
@ -45,7 +39,7 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.applyRequestRules(ctx, r, layer.Revision, options); err != nil {
|
if err := l.applyRequestRules(r, options); err != nil {
|
||||||
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
@ -72,9 +66,7 @@ func (l *Layer) ResponseTransformer(layer *store.Layer) proxy.ResponseTransforme
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Request.Context()
|
if err := l.applyResponseRules(r, options); err != nil {
|
||||||
|
|
||||||
if err := l.applyResponseRules(ctx, r, layer.Revision, options); err != nil {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,30 +75,7 @@ func (l *Layer) ResponseTransformer(layer *store.Layer) proxy.ResponseTransforme
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(funcs ...OptionFunc) *Layer {
|
func New(funcs ...OptionFunc) *Layer {
|
||||||
return &Layer{
|
return &Layer{}
|
||||||
requestRuleEngine: util.NewRevisionedRuleEngine(func(options *LayerOptions) (*rule.Engine[*RequestVars], error) {
|
|
||||||
engine, err := rule.NewEngine[*RequestVars](
|
|
||||||
rule.WithRules(options.Rules.Request...),
|
|
||||||
ruleHTTP.WithRequestFuncs(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine, nil
|
|
||||||
}),
|
|
||||||
responseRuleEngine: util.NewRevisionedRuleEngine(func(options *LayerOptions) (*rule.Engine[*ResponseVars], error) {
|
|
||||||
engine, err := rule.NewEngine[*ResponseVars](
|
|
||||||
rule.WithRules(options.Rules.Response...),
|
|
||||||
ruleHTTP.WithResponseFuncs(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -1,93 +1,68 @@
|
||||||
package rewriter
|
package rewriter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||||
ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
|
ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestVars struct {
|
type RequestEnv struct {
|
||||||
Request RequestVar `expr:"request"`
|
Request RequestInfo `expr:"request"`
|
||||||
OriginalURL URLVar `expr:"original_url"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLVar struct {
|
type URLEnv struct {
|
||||||
Scheme string `expr:"scheme"`
|
Scheme string `expr:"scheme"`
|
||||||
Opaque string `expr:"opaque"`
|
Opaque string `expr:"opaque"`
|
||||||
User UserVar `expr:"user"`
|
User UserInfoEnv `expr:"user"`
|
||||||
Host string `expr:"host"`
|
Host string `expr:"host"`
|
||||||
Path string `expr:"path"`
|
Path string `expr:"path"`
|
||||||
RawPath string `expr:"raw_path"`
|
RawPath string `expr:"rawPath"`
|
||||||
RawQuery string `expr:"raw_query"`
|
RawQuery string `expr:"rawQuery"`
|
||||||
Fragment string `expr:"fragment"`
|
Fragment string `expr:"fragment"`
|
||||||
RawFragment string `expr:"raw_fragment"`
|
RawFragment string `expr:"rawFragment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserVar struct {
|
type UserInfoEnv struct {
|
||||||
Username string `expr:"username"`
|
Username string `expr:"username"`
|
||||||
Password string `expr:"password"`
|
Password string `expr:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestVar struct {
|
type RequestInfo struct {
|
||||||
Method string `expr:"method"`
|
Method string `expr:"method"`
|
||||||
URL URLVar `expr:"url"`
|
URL URLEnv `expr:"url"`
|
||||||
RawURL string `expr:"raw_url"`
|
RawURL string `expr:"rawUrl"`
|
||||||
Proto string `expr:"proto"`
|
Proto string `expr:"proto"`
|
||||||
ProtoMajor int `expr:"proto_major"`
|
ProtoMajor int `expr:"protoMajor"`
|
||||||
ProtoMinor int `expr:"proto_minor"`
|
ProtoMinor int `expr:"protoMinor"`
|
||||||
Header map[string][]string `expr:"header"`
|
Header map[string][]string `expr:"header"`
|
||||||
ContentLength int64 `expr:"content_length"`
|
ContentLength int64 `expr:"contentLength"`
|
||||||
TransferEncoding []string `expr:"transfer_encoding"`
|
TransferEncoding []string `expr:"transferEncoding"`
|
||||||
Host string `expr:"host"`
|
Host string `expr:"host"`
|
||||||
Trailer map[string][]string `expr:"trailer"`
|
Trailer map[string][]string `expr:"trailer"`
|
||||||
RemoteAddr string `expr:"remote_addr"`
|
RemoteAddr string `expr:"remoteAddr"`
|
||||||
RequestURI string `expr:"request_uri"`
|
RequestURI string `expr:"requestUri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRevision int, options *LayerOptions) error {
|
func (l *Layer) applyRequestRules(r *http.Request, options *LayerOptions) error {
|
||||||
rules := options.Rules.Request
|
rules := options.Rules.Request
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := l.getRequestRuleEngine(ctx, layerRevision, options)
|
engine, err := l.getRequestRuleEngine(r, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
originalURL, err := director.OriginalURL(ctx)
|
env := &RequestEnv{
|
||||||
if err != nil {
|
Request: RequestInfo{
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := &RequestVars{
|
|
||||||
OriginalURL: URLVar{
|
|
||||||
Scheme: originalURL.Scheme,
|
|
||||||
Opaque: originalURL.Opaque,
|
|
||||||
User: UserVar{
|
|
||||||
Username: originalURL.User.Username(),
|
|
||||||
Password: func() string {
|
|
||||||
passwd, _ := originalURL.User.Password()
|
|
||||||
return passwd
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
Host: originalURL.Host,
|
|
||||||
Path: originalURL.Path,
|
|
||||||
RawPath: originalURL.RawPath,
|
|
||||||
RawQuery: originalURL.RawQuery,
|
|
||||||
Fragment: originalURL.Fragment,
|
|
||||||
RawFragment: originalURL.RawFragment,
|
|
||||||
},
|
|
||||||
Request: RequestVar{
|
|
||||||
Method: r.Method,
|
Method: r.Method,
|
||||||
URL: URLVar{
|
URL: URLEnv{
|
||||||
Scheme: r.URL.Scheme,
|
Scheme: r.URL.Scheme,
|
||||||
Opaque: r.URL.Opaque,
|
Opaque: r.URL.Opaque,
|
||||||
User: UserVar{
|
User: UserInfoEnv{
|
||||||
Username: r.URL.User.Username(),
|
Username: r.URL.User.Username(),
|
||||||
Password: func() string {
|
Password: func() string {
|
||||||
passwd, _ := r.URL.User.Password()
|
passwd, _ := r.URL.User.Password()
|
||||||
|
@ -115,17 +90,18 @@ func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRev
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ruleHTTP.WithRequest(ctx, r)
|
if _, err := engine.Apply(env); err != nil {
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) getRequestRuleEngine(ctx context.Context, layerRevision int, options *LayerOptions) (*rule.Engine[*RequestVars], error) {
|
func (l *Layer) getRequestRuleEngine(r *http.Request, options *LayerOptions) (*rule.Engine[*RequestEnv], error) {
|
||||||
engine, err := l.requestRuleEngine.Get(ctx, layerRevision, options)
|
engine, err := rule.NewEngine[*RequestEnv](
|
||||||
|
rule.WithRules(options.Rules.Request...),
|
||||||
|
ruleHTTP.WithRequestFuncs(r),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
@ -133,65 +109,42 @@ func (l *Layer) getRequestRuleEngine(ctx context.Context, layerRevision int, opt
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseVars struct {
|
type ResponseEnv struct {
|
||||||
OriginalURL URLVar `expr:"original_url"`
|
Request RequestInfo `expr:"request"`
|
||||||
Request RequestVar `expr:"request"`
|
Response ResponseInfo `expr:"response"`
|
||||||
Response ResponseVar `expr:"response"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseVar struct {
|
type ResponseInfo struct {
|
||||||
Status string `expr:"status"`
|
Status string `expr:"status"`
|
||||||
StatusCode int `expr:"status_code"`
|
StatusCode int `expr:"statusCode"`
|
||||||
Proto string `expr:"proto"`
|
Proto string `expr:"proto"`
|
||||||
ProtoMajor int `expr:"proto_major"`
|
ProtoMajor int `expr:"protoMajor"`
|
||||||
ProtoMinor int `expr:"proto_minor"`
|
ProtoMinor int `expr:"protoMinor"`
|
||||||
Header map[string][]string `expr:"header"`
|
Header map[string][]string `expr:"header"`
|
||||||
ContentLength int64 `expr:"content_length"`
|
ContentLength int64 `expr:"contentLength"`
|
||||||
TransferEncoding []string `expr:"transfer_encoding"`
|
TransferEncoding []string `expr:"transferEncoding"`
|
||||||
Uncompressed bool `expr:"uncompressed"`
|
Uncompressed bool `expr:"uncompressed"`
|
||||||
Trailer map[string][]string `expr:"trailer"`
|
Trailer map[string][]string `expr:"trailer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerRevision int, options *LayerOptions) error {
|
func (l *Layer) applyResponseRules(r *http.Response, options *LayerOptions) error {
|
||||||
rules := options.Rules.Response
|
rules := options.Rules.Response
|
||||||
if len(rules) == 0 {
|
if len(rules) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := l.getResponseRuleEngine(ctx, layerRevision, options)
|
engine, err := l.getResponseRuleEngine(r, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
originalURL, err := director.OriginalURL(ctx)
|
env := &ResponseEnv{
|
||||||
if err != nil {
|
Request: RequestInfo{
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := &ResponseVars{
|
|
||||||
OriginalURL: URLVar{
|
|
||||||
Scheme: originalURL.Scheme,
|
|
||||||
Opaque: originalURL.Opaque,
|
|
||||||
User: UserVar{
|
|
||||||
Username: originalURL.User.Username(),
|
|
||||||
Password: func() string {
|
|
||||||
passwd, _ := originalURL.User.Password()
|
|
||||||
return passwd
|
|
||||||
}(),
|
|
||||||
},
|
|
||||||
Host: originalURL.Host,
|
|
||||||
Path: originalURL.Path,
|
|
||||||
RawPath: originalURL.RawPath,
|
|
||||||
RawQuery: originalURL.RawQuery,
|
|
||||||
Fragment: originalURL.Fragment,
|
|
||||||
RawFragment: originalURL.RawFragment,
|
|
||||||
},
|
|
||||||
Request: RequestVar{
|
|
||||||
Method: r.Request.Method,
|
Method: r.Request.Method,
|
||||||
URL: URLVar{
|
URL: URLEnv{
|
||||||
Scheme: r.Request.URL.Scheme,
|
Scheme: r.Request.URL.Scheme,
|
||||||
Opaque: r.Request.URL.Opaque,
|
Opaque: r.Request.URL.Opaque,
|
||||||
User: UserVar{
|
User: UserInfoEnv{
|
||||||
Username: r.Request.URL.User.Username(),
|
Username: r.Request.URL.User.Username(),
|
||||||
Password: func() string {
|
Password: func() string {
|
||||||
passwd, _ := r.Request.URL.User.Password()
|
passwd, _ := r.Request.URL.User.Password()
|
||||||
|
@ -217,7 +170,7 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||||
RemoteAddr: r.Request.RemoteAddr,
|
RemoteAddr: r.Request.RemoteAddr,
|
||||||
RequestURI: r.Request.RequestURI,
|
RequestURI: r.Request.RequestURI,
|
||||||
},
|
},
|
||||||
Response: ResponseVar{
|
Response: ResponseInfo{
|
||||||
Proto: r.Proto,
|
Proto: r.Proto,
|
||||||
ProtoMajor: r.ProtoMajor,
|
ProtoMajor: r.ProtoMajor,
|
||||||
ProtoMinor: r.ProtoMinor,
|
ProtoMinor: r.ProtoMinor,
|
||||||
|
@ -230,17 +183,18 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = ruleHTTP.WithResponse(ctx, r)
|
if _, err := engine.Apply(env); err != nil {
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layer) getResponseRuleEngine(ctx context.Context, layerRevision int, options *LayerOptions) (*rule.Engine[*ResponseVars], error) {
|
func (l *Layer) getResponseRuleEngine(r *http.Response, options *LayerOptions) (*rule.Engine[*ResponseEnv], error) {
|
||||||
engine, err := l.responseRuleEngine.Get(ctx, layerRevision, options)
|
engine, err := rule.NewEngine[*ResponseEnv](
|
||||||
|
rule.WithRules(options.Rules.Response...),
|
||||||
|
ruleHTTP.WithResponseFuncs(r),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RuleEngineFactoryFunc[V any, O any] func(ops O) (*rule.Engine[V], error)
|
|
||||||
|
|
||||||
type RevisionedRuleEngine[V any, O any] struct {
|
|
||||||
mutex sync.RWMutex
|
|
||||||
revision int
|
|
||||||
engine *rule.Engine[V]
|
|
||||||
factory RuleEngineFactoryFunc[V, O]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RevisionedRuleEngine[V, O]) Get(ctx context.Context, revision int, opts O) (*rule.Engine[V], error) {
|
|
||||||
e.mutex.RLock()
|
|
||||||
if revision == e.revision {
|
|
||||||
logger.Debug(ctx, "using cached rule engine", logger.F("layerRevision", revision))
|
|
||||||
|
|
||||||
defer e.mutex.RUnlock()
|
|
||||||
return e.engine, nil
|
|
||||||
}
|
|
||||||
e.mutex.RUnlock()
|
|
||||||
|
|
||||||
e.mutex.Lock()
|
|
||||||
defer e.mutex.Unlock()
|
|
||||||
|
|
||||||
logger.Debug(ctx, "creating rule engine", logger.F("layerRevision", revision))
|
|
||||||
|
|
||||||
engine, err := e.factory(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e.engine = engine
|
|
||||||
e.revision = revision
|
|
||||||
|
|
||||||
return engine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRevisionedRuleEngine[V any, O any](factory RuleEngineFactoryFunc[V, O]) *RevisionedRuleEngine[V, O] {
|
|
||||||
return &RevisionedRuleEngine[V, O]{
|
|
||||||
factory: factory,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) initRepositories(ctx context.Context) error {
|
func (s *Server) initRepositories(ctx context.Context) error {
|
||||||
client := setup.NewSharedClient(s.redisConfig)
|
client := setup.NewRedisClient(ctx, s.redisConfig)
|
||||||
|
|
||||||
if err := s.initProxyRepository(ctx, client); err != nil {
|
if err := s.initProxyRepository(ctx, client); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/http/pprof"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -147,34 +146,6 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.serverConfig.Profiling.Enabled {
|
|
||||||
profiling := s.serverConfig.Profiling
|
|
||||||
logger.Info(ctx, "enabling profiling", logger.F("endpoint", profiling.Endpoint))
|
|
||||||
|
|
||||||
router.Group(func(r chi.Router) {
|
|
||||||
if profiling.BasicAuth != nil {
|
|
||||||
logger.Info(ctx, "enabling authentication on metrics endpoint")
|
|
||||||
|
|
||||||
r.Use(middleware.BasicAuth(
|
|
||||||
"profiling",
|
|
||||||
profiling.BasicAuth.CredentialsMap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Route(string(profiling.Endpoint), func(r chi.Router) {
|
|
||||||
r.HandleFunc("/", pprof.Index)
|
|
||||||
r.HandleFunc("/cmdline", pprof.Cmdline)
|
|
||||||
r.HandleFunc("/profile", pprof.Profile)
|
|
||||||
r.HandleFunc("/symbol", pprof.Symbol)
|
|
||||||
r.HandleFunc("/trace", pprof.Trace)
|
|
||||||
r.HandleFunc("/{name}", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := chi.URLParam(r, "name")
|
|
||||||
pprof.Handler(name).ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Group(func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
r.Use(director.Middleware())
|
r.Use(director.Middleware())
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,16 @@
|
||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/expr-lang/expr"
|
"github.com/expr-lang/expr"
|
||||||
"github.com/expr-lang/expr/vm"
|
"github.com/expr-lang/expr/vm"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Engine[V any] struct {
|
type Engine[E any] struct {
|
||||||
rules []*vm.Program
|
rules []*vm.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine[V]) Apply(ctx context.Context, vars V) ([]any, error) {
|
func (e *Engine[E]) Apply(env E) ([]any, error) {
|
||||||
type Env[V any] struct {
|
|
||||||
Context context.Context `expr:"ctx"`
|
|
||||||
Vars V `expr:"vars"`
|
|
||||||
}
|
|
||||||
|
|
||||||
env := Env[V]{
|
|
||||||
Context: ctx,
|
|
||||||
Vars: vars,
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make([]any, 0, len(e.rules))
|
results := make([]any, 0, len(e.rules))
|
||||||
for i, r := range e.rules {
|
for i, r := range e.rules {
|
||||||
result, err := expr.Run(r, env)
|
result, err := expr.Run(r, env)
|
||||||
|
@ -54,26 +42,3 @@ func NewEngine[E any](funcs ...OptionFunc) (*Engine[E], error) {
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Context[T any](ctx context.Context, key any) (T, bool) {
|
|
||||||
raw := ctx.Value(key)
|
|
||||||
if raw == nil {
|
|
||||||
return *new(T), false
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := Assert[T](raw)
|
|
||||||
if err != nil {
|
|
||||||
return *new(T), false
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func Assert[T any](raw any) (T, error) {
|
|
||||||
value, ok := raw.(T)
|
|
||||||
if !ok {
|
|
||||||
return *new(T), errors.Errorf("unexpected value '%T'", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
)
|
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
const (
|
|
||||||
contextKeyRequest contextKey = "request"
|
|
||||||
contextKeyResponse contextKey = "response"
|
|
||||||
)
|
|
||||||
|
|
||||||
func WithRequest(ctx context.Context, r *http.Request) context.Context {
|
|
||||||
return context.WithValue(ctx, contextKeyRequest, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithResponse(ctx context.Context, r *http.Response) context.Context {
|
|
||||||
return context.WithValue(ctx, contextKeyResponse, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ctxRequest(ctx context.Context) (*http.Request, bool) {
|
|
||||||
return rule.Context[*http.Request](ctx, contextKeyRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ctxResponse(ctx context.Context) (*http.Response, bool) {
|
|
||||||
return rule.Context[*http.Response](ctx, contextKeyResponse)
|
|
||||||
}
|
|
|
@ -1,18 +1,20 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||||
"github.com/expr-lang/expr"
|
"github.com/expr-lang/expr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithRequestFuncs() rule.OptionFunc {
|
func WithRequestFuncs(r *http.Request) rule.OptionFunc {
|
||||||
return func(opts *rule.Options) {
|
return func(opts *rule.Options) {
|
||||||
funcs := []expr.Option{
|
funcs := []expr.Option{
|
||||||
setRequestURLFunc(),
|
setRequestURL(r),
|
||||||
setRequestHeaderFunc(),
|
setRequestHeaderFunc(r),
|
||||||
addRequestHeaderFunc(),
|
addRequestHeaderFunc(r),
|
||||||
delRequestHeadersFunc(),
|
delRequestHeadersFunc(r),
|
||||||
setRequestHostFunc(),
|
setRequestHostFunc(r),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
if len(opts.Expr) == 0 {
|
||||||
|
@ -23,12 +25,12 @@ func WithRequestFuncs() rule.OptionFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithResponseFuncs() rule.OptionFunc {
|
func WithResponseFuncs(r *http.Response) rule.OptionFunc {
|
||||||
return func(opts *rule.Options) {
|
return func(opts *rule.Options) {
|
||||||
funcs := []expr.Option{
|
funcs := []expr.Option{
|
||||||
setResponseHeaderFunc(),
|
setResponseHeaderFunc(r),
|
||||||
addResponseHeaderFunc(),
|
addResponseHeaderFunc(r),
|
||||||
delResponseHeadersFunc(),
|
delResponseHeadersFunc(r),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Expr) == 0 {
|
if len(opts.Expr) == 0 {
|
||||||
|
|
|
@ -1,155 +1,109 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/expr-lang/expr"
|
"github.com/expr-lang/expr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setRequestHostFunc() expr.Option {
|
func setRequestHostFunc(r *http.Request) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"set_host",
|
"set_host",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
host := params[0].(string)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
host, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := ctxRequest(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Host = host
|
r.Host = host
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string) bool),
|
new(func(string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRequestURLFunc() expr.Option {
|
func setRequestURL(r *http.Request) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"set_url",
|
"set_url",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
rawURL := params[0].(string)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawURL, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(rawURL)
|
url, err := url.Parse(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.WithStack(err)
|
return false, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := ctxRequest(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.URL = url
|
r.URL = url
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string) bool),
|
new(func(string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addRequestHeaderFunc() expr.Option {
|
func addRequestHeaderFunc(r *http.Request) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"add_header",
|
"add_header",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
name := params[0].(string)
|
||||||
if err != nil {
|
rawValue := params[1]
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
var value string
|
||||||
if err != nil {
|
switch v := rawValue.(type) {
|
||||||
return nil, errors.WithStack(err)
|
case []string:
|
||||||
}
|
value = strings.Join(v, ",")
|
||||||
|
case time.Time:
|
||||||
value := formatValue(params[2])
|
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||||
|
case time.Duration:
|
||||||
r, ok := ctxRequest(ctx)
|
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||||
if !ok {
|
default:
|
||||||
return nil, errors.New("could not find http request in context")
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Add(name, value)
|
r.Header.Add(name, value)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string, string) bool),
|
new(func(string, string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRequestHeaderFunc() expr.Option {
|
func setRequestHeaderFunc(r *http.Request) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"set_header",
|
"set_header",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
name := params[0].(string)
|
||||||
if err != nil {
|
rawValue := params[1]
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
var value string
|
||||||
if err != nil {
|
switch v := rawValue.(type) {
|
||||||
return nil, errors.WithStack(err)
|
case []string:
|
||||||
}
|
value = strings.Join(v, ",")
|
||||||
|
case time.Time:
|
||||||
value := formatValue(params[2])
|
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||||
|
case time.Duration:
|
||||||
r, ok := ctxRequest(ctx)
|
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||||
if !ok {
|
default:
|
||||||
return nil, errors.New("could not find http request in context")
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set(name, value)
|
r.Header.Set(name, value)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string, string) bool),
|
new(func(string, string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func delRequestHeadersFunc() expr.Option {
|
func delRequestHeadersFunc(r *http.Request) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"del_headers",
|
"del_headers",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
pattern := params[0].(string)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := ctxRequest(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http request in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted := false
|
deleted := false
|
||||||
|
|
||||||
for key := range r.Header {
|
for key := range r.Header {
|
||||||
|
@ -163,21 +117,6 @@ func delRequestHeadersFunc() expr.Option {
|
||||||
|
|
||||||
return deleted, nil
|
return deleted, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string) bool),
|
new(func(string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatValue(v any) string {
|
|
||||||
var value string
|
|
||||||
switch v := v.(type) {
|
|
||||||
case []string:
|
|
||||||
value = strings.Join(v, ",")
|
|
||||||
case time.Time:
|
|
||||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
|
||||||
case time.Duration:
|
|
||||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
|
||||||
default:
|
|
||||||
value = fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,195 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSetRequestHost(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewHost string `expr:"newHost"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(setRequestHostFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"set_host(ctx, vars.newHost)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewHost: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.NewHost, req.Host; e != g {
|
|
||||||
t.Errorf("req.Host: expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetRequestURL(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewURL string `expr:"newURL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(setRequestURLFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"set_url(ctx, vars.newURL)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewURL: "http://localhost",
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.NewURL, req.URL.String(); e != g {
|
|
||||||
t.Errorf("req.URL.String(): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddRequestHeader(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewHeaderKey string `expr:"newHeaderKey"`
|
|
||||||
NewHeaderValue string `expr:"newHeaderValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(addRequestHeaderFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"add_header(ctx, vars.newHeaderKey, vars.newHeaderValue)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewHeaderKey: "X-My-Header",
|
|
||||||
NewHeaderValue: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.NewHeaderValue, req.Header.Get(vars.NewHeaderKey); e != g {
|
|
||||||
t.Errorf("req.Header.Get(vars.NewHeaderKey): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetRequestHeader(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
HeaderKey string `expr:"headerKey"`
|
|
||||||
HeaderValue string `expr:"headerValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(setRequestHeaderFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"set_header(ctx, vars.headerKey, vars.headerValue)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
HeaderKey: "X-My-Header",
|
|
||||||
HeaderValue: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set(vars.HeaderKey, "test")
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.HeaderValue, req.Header.Get(vars.HeaderKey); e != g {
|
|
||||||
t.Errorf("req.Header.Get(vars.HeaderKey): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelRequestHeaders(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
HeaderPattern string `expr:"headerPattern"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(delRequestHeadersFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"del_headers(ctx, vars.headerPattern)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
HeaderPattern: "X-My-*",
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("X-My-Header", "test")
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithRequest(ctx, req)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := req.Header.Get("X-My-Header"); val != "" {
|
|
||||||
t.Errorf("req.Header.Get(\"X-My-Header\") should be empty, got '%v'", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRuleEngine[V any](t *testing.T, funcs ...rule.OptionFunc) *rule.Engine[V] {
|
|
||||||
engine, err := rule.NewEngine[V](funcs...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return engine
|
|
||||||
}
|
|
|
@ -1,33 +1,22 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/expr-lang/expr"
|
"github.com/expr-lang/expr"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func addResponseHeaderFunc() expr.Option {
|
func addResponseHeaderFunc(r *http.Response) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"add_header",
|
"add_header",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
name := params[0].(string)
|
||||||
if err != nil {
|
rawValue := params[1]
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawValue := params[2]
|
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
switch v := rawValue.(type) {
|
switch v := rawValue.(type) {
|
||||||
|
@ -41,34 +30,20 @@ func addResponseHeaderFunc() expr.Option {
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := ctxResponse(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http response in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Add(name, value)
|
r.Header.Add(name, value)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string, string) bool),
|
new(func(string, string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setResponseHeaderFunc() expr.Option {
|
func setResponseHeaderFunc(r *http.Response) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"set_header",
|
"set_header",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
name := params[0].(string)
|
||||||
if err != nil {
|
rawValue := params[1]
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawValue := params[2]
|
|
||||||
|
|
||||||
var value string
|
var value string
|
||||||
switch v := rawValue.(type) {
|
switch v := rawValue.(type) {
|
||||||
|
@ -82,38 +57,19 @@ func setResponseHeaderFunc() expr.Option {
|
||||||
value = fmt.Sprintf("%v", rawValue)
|
value = fmt.Sprintf("%v", rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, ok := ctxResponse(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http response in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Header.Set(name, value)
|
r.Header.Set(name, value)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string, string) bool),
|
new(func(string, string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func delResponseHeadersFunc() expr.Option {
|
func delResponseHeadersFunc(r *http.Response) expr.Option {
|
||||||
return expr.Function(
|
return expr.Function(
|
||||||
"del_headers",
|
"del_headers",
|
||||||
func(params ...any) (any, error) {
|
func(params ...any) (any, error) {
|
||||||
ctx, err := rule.Assert[context.Context](params[0])
|
pattern := params[0].(string)
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern, err := rule.Assert[string](params[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := ctxResponse(ctx)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("could not find http response in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
deleted := false
|
deleted := false
|
||||||
|
|
||||||
for key := range r.Header {
|
for key := range r.Header {
|
||||||
|
@ -127,6 +83,6 @@ func delResponseHeadersFunc() expr.Option {
|
||||||
|
|
||||||
return deleted, nil
|
return deleted, nil
|
||||||
},
|
},
|
||||||
new(func(context.Context, string) bool),
|
new(func(string) bool),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddResponseHeader(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
NewHeaderKey string `expr:"newHeaderKey"`
|
|
||||||
NewHeaderValue string `expr:"newHeaderValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(addResponseHeaderFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"add_header(ctx, vars.newHeaderKey, vars.newHeaderValue)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := createResponse(req, http.StatusOK, nil)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ctx = WithResponse(ctx, resp)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
NewHeaderKey: "X-My-Header",
|
|
||||||
NewHeaderValue: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.NewHeaderValue, resp.Header.Get(vars.NewHeaderKey); e != g {
|
|
||||||
t.Errorf("resp.Header.Get(vars.NewHeaderKey): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseSetHeader(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
HeaderKey string `expr:"headerKey"`
|
|
||||||
HeaderValue string `expr:"headerValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(setResponseHeaderFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"set_header(ctx, vars.headerKey, vars.headerValue)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := createResponse(req, http.StatusOK, nil)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
HeaderKey: "X-My-Header",
|
|
||||||
HeaderValue: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Header.Set(vars.HeaderKey, "test")
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithResponse(ctx, resp)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if e, g := vars.HeaderValue, resp.Header.Get(vars.HeaderKey); e != g {
|
|
||||||
t.Errorf("resp.Header.Get(vars.HeaderKey): expected '%v', got '%v'", e, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseDelHeaders(t *testing.T) {
|
|
||||||
type Vars struct {
|
|
||||||
HeaderPattern string `expr:"headerPattern"`
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := createRuleEngine[Vars](t,
|
|
||||||
rule.WithExpr(delResponseHeadersFunc()),
|
|
||||||
rule.WithRules(
|
|
||||||
"del_headers(ctx, vars.headerPattern)",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", "http://example.net", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := createResponse(req, http.StatusOK, nil)
|
|
||||||
|
|
||||||
vars := Vars{
|
|
||||||
HeaderPattern: "X-My-*",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Header.Set("X-My-Header", "test")
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = WithResponse(ctx, resp)
|
|
||||||
|
|
||||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
|
||||||
t.Fatalf("%+v", errors.WithStack(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if val := resp.Header.Get("X-My-Header"); val != "" {
|
|
||||||
t.Errorf("resp.Header.Get(\"X-My-Header\") should be empty, got '%v'", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createResponse(req *http.Request, statusCode int, body io.Reader) *http.Response {
|
|
||||||
return &http.Response{
|
|
||||||
Status: http.StatusText(statusCode),
|
|
||||||
StatusCode: statusCode,
|
|
||||||
Proto: "HTTP/1.1",
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Body: io.NopCloser(body),
|
|
||||||
ContentLength: -1,
|
|
||||||
Request: req,
|
|
||||||
Header: make(http.Header, 0),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupAuthnOIDCLayer(conf *config.Config) (director.Layer, error) {
|
func setupAuthnOIDCLayer(conf *config.Config) (director.Layer, error) {
|
||||||
rdb := NewSharedClient(conf.Redis)
|
rdb := newRedisClient(conf.Redis)
|
||||||
adapter := redis.NewStoreAdapter(rdb)
|
adapter := redis.NewStoreAdapter(rdb)
|
||||||
store := session.NewStore(adapter)
|
store := session.NewStore(adapter)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func SetupIntegrations(ctx context.Context, conf *config.Config) ([]integration.
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
|
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
|
||||||
client := NewSharedClient(conf.Redis)
|
client := newRedisClient(conf.Redis)
|
||||||
locker := redis.NewLocker(client, 10)
|
locker := redis.NewLocker(client, 10)
|
||||||
|
|
||||||
integration := kubernetes.NewIntegration(
|
integration := kubernetes.NewIntegration(
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
|
func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
|
||||||
client := NewSharedClient(conf.Redis)
|
client := newRedisClient(conf.Redis)
|
||||||
locker := redis.NewLocker(client, int(conf.Redis.LockMaxRetries))
|
locker := redis.NewLocker(client, int(conf.Redis.LockMaxRetries))
|
||||||
return locker, nil
|
return locker, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,19 @@ package setup
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
redisStore "forge.cadoles.com/cadoles/bouncer/internal/store/redis"
|
redisStore "forge.cadoles.com/cadoles/bouncer/internal/store/redis"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewRedisClient(ctx context.Context, conf config.RedisConfig) redis.UniversalClient {
|
||||||
|
return redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
|
Addrs: conf.Adresses,
|
||||||
|
MasterName: string(conf.Master),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func NewProxyRepository(ctx context.Context, client redis.UniversalClient) (store.ProxyRepository, error) {
|
func NewProxyRepository(ctx context.Context, client redis.UniversalClient) (store.ProxyRepository, error) {
|
||||||
return redisStore.NewProxyRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay), nil
|
return redisStore.NewProxyRepository(client, redisStore.DefaultTxMaxAttempts, redisStore.DefaultTxBaseDelay), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,6 @@ func setupQueueLayer(conf *config.Config) (director.Layer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newQueueAdapter(redisConf config.RedisConfig) (queue.Adapter, error) {
|
func newQueueAdapter(redisConf config.RedisConfig) (queue.Adapter, error) {
|
||||||
rdb := NewSharedClient(redisConf)
|
rdb := newRedisClient(redisConf)
|
||||||
return queueRedis.NewAdapter(rdb, 2), nil
|
return queueRedis.NewAdapter(rdb, 2), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,14 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var clients sync.Map
|
|
||||||
|
|
||||||
func NewSharedClient(conf config.RedisConfig) redis.UniversalClient {
|
|
||||||
key := strings.Join(conf.Adresses, "|") + "|" + string(conf.Master)
|
|
||||||
|
|
||||||
value, exists := clients.Load(key)
|
|
||||||
if exists {
|
|
||||||
if client, ok := (value).(redis.UniversalClient); ok {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client := newRedisClient(conf)
|
|
||||||
|
|
||||||
clients.Store(key, client)
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRedisClient(conf config.RedisConfig) redis.UniversalClient {
|
func newRedisClient(conf config.RedisConfig) redis.UniversalClient {
|
||||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
return redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
Addrs: conf.Adresses,
|
Addrs: conf.Adresses,
|
||||||
MasterName: string(conf.Master),
|
MasterName: string(conf.Master),
|
||||||
ReadTimeout: time.Duration(conf.ReadTimeout),
|
ReadTimeout: time.Duration(conf.ReadTimeout),
|
||||||
|
@ -40,33 +16,5 @@ func newRedisClient(conf config.RedisConfig) redis.UniversalClient {
|
||||||
DialTimeout: time.Duration(conf.DialTimeout),
|
DialTimeout: time.Duration(conf.DialTimeout),
|
||||||
RouteByLatency: true,
|
RouteByLatency: true,
|
||||||
ContextTimeoutEnabled: true,
|
ContextTimeoutEnabled: true,
|
||||||
MaxRetries: int(conf.MaxRetries),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
go func() {
|
|
||||||
ctx := logger.With(context.Background(),
|
|
||||||
logger.F("adresses", conf.Adresses),
|
|
||||||
logger.F("master", conf.Master),
|
|
||||||
)
|
|
||||||
|
|
||||||
timer := time.NewTicker(time.Duration(conf.PingInterval))
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
connected := true
|
|
||||||
|
|
||||||
for range timer.C {
|
|
||||||
if _, err := client.Ping(ctx).Result(); err != nil {
|
|
||||||
logger.Error(ctx, "redis disconnected", logger.E(errors.WithStack(err)))
|
|
||||||
connected = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !connected {
|
|
||||||
logger.Info(ctx, "redis reconnected")
|
|
||||||
connected = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,14 +91,12 @@ func WithRetry(ctx context.Context, client redis.UniversalClient, key string, fn
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStack(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "redis error", logger.E(errors.WithStack(err)))
|
|
||||||
|
|
||||||
return errors.WithStack(redis.TxFailedErr)
|
return errors.WithStack(redis.TxFailedErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,19 +49,6 @@ admin:
|
||||||
# Mettre à null pour désactiver l'authentification
|
# Mettre à null pour désactiver l'authentification
|
||||||
basicAuth: null
|
basicAuth: null
|
||||||
|
|
||||||
# Profiling
|
|
||||||
profiling:
|
|
||||||
# Activer ou désactiver les endpoints de profiling
|
|
||||||
enabled: true
|
|
||||||
# Route de publication des endpoints de profiling
|
|
||||||
endpoint: /.bouncer/profiling
|
|
||||||
# Authentification "basic auth" sur les endpoints
|
|
||||||
# de profiling
|
|
||||||
# Mettre à null pour désactiver l'authentification
|
|
||||||
basicAuth:
|
|
||||||
credentials:
|
|
||||||
prof: iling
|
|
||||||
|
|
||||||
# Configuration de l'intégration Sentry
|
# Configuration de l'intégration Sentry
|
||||||
# Voir https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
# Voir https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
||||||
sentry:
|
sentry:
|
||||||
|
@ -72,7 +59,7 @@ admin:
|
||||||
sampleRate: 1
|
sampleRate: 1
|
||||||
enableTracing: true
|
enableTracing: true
|
||||||
tracesSampleRate: 0.2
|
tracesSampleRate: 0.2
|
||||||
profilesSampleRate: 0.2
|
profilesSampleRate: 1
|
||||||
ignoreErrors: []
|
ignoreErrors: []
|
||||||
sendDefaultPII: false
|
sendDefaultPII: false
|
||||||
serverName: ""
|
serverName: ""
|
||||||
|
@ -112,19 +99,6 @@ proxy:
|
||||||
credentials:
|
credentials:
|
||||||
prom: etheus
|
prom: etheus
|
||||||
|
|
||||||
# Profiling
|
|
||||||
profiling:
|
|
||||||
# Activer ou désactiver les endpoints de profiling
|
|
||||||
enabled: true
|
|
||||||
# Route de publication des endpoints de profiling
|
|
||||||
endpoint: /.bouncer/profiling
|
|
||||||
# Authentification "basic auth" sur les endpoints
|
|
||||||
# de profiling
|
|
||||||
# Mettre à null pour désactiver l'authentification
|
|
||||||
basicAuth:
|
|
||||||
credentials:
|
|
||||||
prof: iling
|
|
||||||
|
|
||||||
# Configuration de la mise en cache
|
# Configuration de la mise en cache
|
||||||
# locale des données proxy/layers
|
# locale des données proxy/layers
|
||||||
cache:
|
cache:
|
||||||
|
@ -190,8 +164,6 @@ redis:
|
||||||
writeTimeout: 30s
|
writeTimeout: 30s
|
||||||
readTimeout: 30s
|
readTimeout: 30s
|
||||||
dialTimeout: 30s
|
dialTimeout: 30s
|
||||||
maxRetries: 3
|
|
||||||
pingInterval: 30s
|
|
||||||
|
|
||||||
# Configuration des logs
|
# Configuration des logs
|
||||||
logger:
|
logger:
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
# Updated by Siege %_VERSION%, %_DATE%
|
|
||||||
# Copyright 2000-2016 by %_AUTHOR%
|
|
||||||
#
|
|
||||||
# Siege configuration file -- edit as necessary
|
|
||||||
# For more information about configuring and running this program,
|
|
||||||
# visit: http://www.joedog.org/
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Verbose mode: With this feature enabled, siege will print the
|
|
||||||
# result of each transaction to stdout. (Enabled by default)
|
|
||||||
#
|
|
||||||
# ex: verbose = true|false
|
|
||||||
#
|
|
||||||
verbose = true
|
|
||||||
|
|
||||||
#
|
|
||||||
# Color mode: This option works in conjunction with verbose mode.
|
|
||||||
# It tells siege whether or not it should display its output in
|
|
||||||
# color-coded output. (Enabled by default)
|
|
||||||
#
|
|
||||||
# ex: color = on | off
|
|
||||||
#
|
|
||||||
color = on
|
|
||||||
|
|
||||||
#
|
|
||||||
# Cache revalidation. Siege supports cache revalidation for both ETag
|
|
||||||
# and Last-modified headers. If a copy is still fresh, the server
|
|
||||||
# responds with 304. While this feature is required for HTTP/1.1, it
|
|
||||||
# may not be welcomed for load testing. We allow you to breach the
|
|
||||||
# protocol and turn off caching
|
|
||||||
#
|
|
||||||
# HTTP/1.1 200 0.00 secs: 2326 bytes ==> /apache_pb.gif
|
|
||||||
# HTTP/1.1 304 0.00 secs: 0 bytes ==> /apache_pb.gif
|
|
||||||
# HTTP/1.1 304 0.00 secs: 0 bytes ==> /apache_pb.gif
|
|
||||||
#
|
|
||||||
# Siege also supports Cache-control headers. Consider this server
|
|
||||||
# response: Cache-Control: max-age=3
|
|
||||||
# That tells siege to cache the file for three seconds. While it
|
|
||||||
# doesn't actually store the file, it will logically grab it from
|
|
||||||
# its cache. In verbose output, it designates a cached resource
|
|
||||||
# with (c):
|
|
||||||
#
|
|
||||||
# HTTP/1.1 200 0.25 secs: 159 bytes ==> GET /expires/
|
|
||||||
# HTTP/1.1 200 1.48 secs: 498419 bytes ==> GET /expires/Otter_in_Southwold.jpg
|
|
||||||
# HTTP/1.1 200 0.24 secs: 159 bytes ==> GET /expires/
|
|
||||||
# HTTP/1.1 200(C) 0.00 secs: 0 bytes ==> GET /expires/Otter_in_Southwold.jpg
|
|
||||||
#
|
|
||||||
# NOTE: with color enabled, cached URLs appear in green
|
|
||||||
#
|
|
||||||
# ex: cache = true
|
|
||||||
#
|
|
||||||
cache = true
|
|
||||||
|
|
||||||
#
|
|
||||||
# Cookie support: by default siege accepts cookies. This directive is
|
|
||||||
# available to disable that support. Set cookies to 'false' to refuse
|
|
||||||
# cookies. Set it to 'true' to accept them. The default value is true.
|
|
||||||
# If you want to maintain state with the server, then this MUST be set
|
|
||||||
# to true.
|
|
||||||
#
|
|
||||||
# ex: cookies = false
|
|
||||||
#
|
|
||||||
cookies = true
|
|
||||||
|
|
||||||
#
|
|
||||||
# Failures: This is the number of total connection failures allowed
|
|
||||||
# before siege aborts. Connection failures (timeouts, socket failures,
|
|
||||||
# etc.) are combined with 400 and 500 level errors in the final stats,
|
|
||||||
# but those errors do not count against the abort total. If you set
|
|
||||||
# this total to 10, then siege will abort after ten socket timeouts,
|
|
||||||
# but it will NOT abort after ten 404s. This is designed to prevent a
|
|
||||||
# run-away mess on an unattended siege.
|
|
||||||
#
|
|
||||||
# The default value is 1024
|
|
||||||
#
|
|
||||||
# ex: failures = 50
|
|
||||||
#
|
|
||||||
failures = -1
|
|
Loading…
Reference in New Issue