feat: initial commit
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good

This commit is contained in:
2023-04-24 20:52:12 +02:00
commit e66938f1d3
134 changed files with 8507 additions and 0 deletions

View File

@ -0,0 +1,60 @@
package director
import (
"context"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
type contextKey string
const (
contextKeyProxy contextKey = "proxy"
contextKeyLayers contextKey = "layers"
)
var (
errContextKeyNotFound = errors.New("context key not found")
errUnexpectedContextValue = errors.New("unexpected context value")
)
func withProxy(ctx context.Context, proxy *store.Proxy) context.Context {
return context.WithValue(ctx, contextKeyProxy, proxy)
}
func ctxProxy(ctx context.Context) (*store.Proxy, error) {
proxy, err := ctxValue[*store.Proxy](ctx, contextKeyProxy)
if err != nil {
return nil, errors.WithStack(err)
}
return proxy, nil
}
func withLayers(ctx context.Context, layers []*store.Layer) context.Context {
return context.WithValue(ctx, contextKeyLayers, layers)
}
func ctxLayers(ctx context.Context) ([]*store.Layer, error) {
layers, err := ctxValue[[]*store.Layer](ctx, contextKeyLayers)
if err != nil {
return nil, errors.WithStack(err)
}
return layers, nil
}
func ctxValue[T any](ctx context.Context, key contextKey) (T, error) {
raw := ctx.Value(key)
if raw == nil {
return *new(T), errors.WithStack(errContextKeyNotFound)
}
value, ok := raw.(T)
if !ok {
return *new(T), errors.WithStack(errUnexpectedContextValue)
}
return value, nil
}

View File

@ -0,0 +1,231 @@
package director
import (
"context"
"net/http"
"net/url"
"sort"
"forge.cadoles.com/Cadoles/go-proxy"
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type Director struct {
proxyRepository store.ProxyRepository
layerRepository store.LayerRepository
layerRegistry *LayerRegistry
}
func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) {
ctx := r.Context()
proxies, err := d.getProxies(ctx)
if err != nil {
return r, errors.WithStack(err)
}
var match *store.Proxy
MAIN:
for _, p := range proxies {
for _, from := range p.From {
if matches := wildcard.Match(r.Host, from); !matches {
continue
}
match = p
break MAIN
}
}
if match == nil {
return r, nil
}
toURL, err := url.Parse(match.To)
if err != nil {
return r, errors.WithStack(err)
}
r.URL.Host = toURL.Host
r.URL.Scheme = toURL.Scheme
ctx = logger.With(ctx,
logger.F("proxy", match.Name),
logger.F("host", r.Host),
logger.F("remoteAddr", r.RemoteAddr),
)
ctx = withProxy(ctx, match)
layers, err := d.getLayers(ctx, match.Name)
if err != nil {
return r, errors.WithStack(err)
}
ctx = withLayers(ctx, layers)
r = r.WithContext(ctx)
return r, nil
}
func (d *Director) getProxies(ctx context.Context) ([]*store.Proxy, error) {
headers, err := d.proxyRepository.QueryProxy(ctx, store.WithProxyQueryEnabled(true))
if err != nil {
return nil, errors.WithStack(err)
}
sort.Sort(store.ByProxyWeight(headers))
proxies := make([]*store.Proxy, 0, len(headers))
for _, h := range headers {
if !h.Enabled {
continue
}
proxy, err := d.proxyRepository.GetProxy(ctx, h.Name)
if err != nil {
return nil, errors.WithStack(err)
}
proxies = append(proxies, proxy)
}
return proxies, nil
}
func (d *Director) getLayers(ctx context.Context, proxyName store.ProxyName) ([]*store.Layer, error) {
headers, err := d.layerRepository.QueryLayers(ctx, proxyName, store.WithLayerQueryEnabled(true))
if err != nil {
return nil, errors.WithStack(err)
}
sort.Sort(store.ByLayerWeight(headers))
layers := make([]*store.Layer, 0, len(headers))
for _, h := range headers {
if !h.Enabled {
continue
}
layer, err := d.layerRepository.GetLayer(ctx, proxyName, h.Name)
if err != nil {
return nil, errors.WithStack(err)
}
layers = append(layers, layer)
}
return layers, nil
}
func (d *Director) RequestTransformer() proxy.RequestTransformer {
return func(r *http.Request) {
ctx := r.Context()
layers, err := ctxLayers(ctx)
if err != nil {
if errors.Is(err, errContextKeyNotFound) {
return
}
logger.Error(ctx, "could not retrieve layers from context", logger.E(errors.WithStack(err)))
return
}
for _, layer := range layers {
transformerLayer, ok := d.layerRegistry.GetRequestTransformer(layer.Type)
if !ok {
continue
}
transformer := transformerLayer.RequestTransformer(layer)
transformer(r)
}
}
}
func (d *Director) ResponseTransformer() proxy.ResponseTransformer {
return func(r *http.Response) error {
ctx := r.Request.Context()
layers, err := ctxLayers(ctx)
if err != nil {
if errors.Is(err, errContextKeyNotFound) {
return nil
}
return errors.WithStack(err)
}
for _, layer := range layers {
transformerLayer, ok := d.layerRegistry.GetResponseTransformer(layer.Type)
if !ok {
continue
}
transformer := transformerLayer.ResponseTransformer(layer)
if err := transformer(r); err != nil {
return errors.WithStack(err)
}
}
return nil
}
}
func (d *Director) Middleware() proxy.Middleware {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
r, err := d.rewriteRequest(r)
if err != nil {
logger.Error(r.Context(), "could not rewrite request", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
ctx := r.Context()
layers, err := ctxLayers(ctx)
if err != nil {
if errors.Is(err, errContextKeyNotFound) {
return
}
logger.Error(ctx, "could not retrieve proxy and layers from context", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
httpMiddlewares := make([]proxy.Middleware, 0)
for _, layer := range layers {
middleware, ok := d.layerRegistry.GetMiddleware(layer.Type)
if !ok {
continue
}
httpMiddlewares = append(httpMiddlewares, middleware.Middleware(layer))
}
handler := createMiddlewareChain(next, httpMiddlewares)
handler.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
func New(proxyRepository store.ProxyRepository, layerRepository store.LayerRepository, layers ...Layer) *Director {
registry := NewLayerRegistry(layers...)
return &Director{proxyRepository, layerRepository, registry}
}

View File

@ -0,0 +1,104 @@
package director
import (
"forge.cadoles.com/Cadoles/go-proxy"
"forge.cadoles.com/cadoles/bouncer/internal/store"
)
type Layer interface {
LayerType() store.LayerType
}
type MiddlewareLayer interface {
Layer
Middleware(layer *store.Layer) proxy.Middleware
}
type RequestTransformerLayer interface {
Layer
RequestTransformer(layer *store.Layer) proxy.RequestTransformer
}
type ResponseTransformerLayer interface {
Layer
ResponseTransformer(layer *store.Layer) proxy.ResponseTransformer
}
type LayerRegistry struct {
index map[store.LayerType]Layer
}
func (r *LayerRegistry) GetLayer(layerType store.LayerType) (Layer, bool) {
layer, exists := r.index[layerType]
if !exists {
return nil, false
}
return layer, true
}
func (r *LayerRegistry) getLayerAsAny(layerType store.LayerType) (any, bool) {
return r.GetLayer(layerType)
}
func (r *LayerRegistry) GetMiddleware(layerType store.LayerType) (MiddlewareLayer, bool) {
layer, exists := r.getLayerAsAny(layerType)
if !exists {
return nil, false
}
middleware, ok := layer.(MiddlewareLayer)
if !ok {
return nil, false
}
return middleware, true
}
func (r *LayerRegistry) GetResponseTransformer(layerType store.LayerType) (ResponseTransformerLayer, bool) {
layer, exists := r.getLayerAsAny(layerType)
if !exists {
return nil, false
}
transformer, ok := layer.(ResponseTransformerLayer)
if !ok {
return nil, false
}
return transformer, true
}
func (r *LayerRegistry) GetRequestTransformer(layerType store.LayerType) (RequestTransformerLayer, bool) {
layer, exists := r.getLayerAsAny(layerType)
if !exists {
return nil, false
}
transformer, ok := layer.(RequestTransformerLayer)
if !ok {
return nil, false
}
return transformer, true
}
func (r *LayerRegistry) Load(layers ...Layer) {
index := make(map[store.LayerType]Layer)
for _, l := range layers {
layerType := l.LayerType()
index[layerType] = l
}
r.index = index
}
func NewLayerRegistry(layers ...Layer) *LayerRegistry {
registry := &LayerRegistry{
index: make(map[store.LayerType]Layer),
}
registry.Load(layers...)
return registry
}

View File

@ -0,0 +1,18 @@
package director
import (
"net/http"
"forge.cadoles.com/Cadoles/go-proxy"
"forge.cadoles.com/Cadoles/go-proxy/util"
)
func createMiddlewareChain(handler http.Handler, middlewares []proxy.Middleware) http.Handler {
util.Reverse(middlewares)
for _, m := range middlewares {
handler = m(handler)
}
return handler
}