feat: move proxy package from arcad/edge
This commit is contained in:
parent
f7d70da174
commit
e60c441d43
|
@ -5,8 +5,8 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
|
"forge.cadoles.com/Cadoles/emissary/internal/proxy"
|
||||||
edgeProxy "forge.cadoles.com/arcad/edge/pkg/proxy"
|
spec "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
|
||||||
"github.com/mitchellh/hashstructure/v2"
|
"github.com/mitchellh/hashstructure/v2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
@ -18,7 +18,7 @@ type proxyEntry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
proxies map[proxy.ID]*proxyEntry
|
proxies map[spec.ID]*proxyEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name implements node.Controller.
|
// Name implements node.Controller.
|
||||||
|
@ -28,9 +28,9 @@ func (c *Controller) Name() string {
|
||||||
|
|
||||||
// Reconcile implements node.Controller.
|
// Reconcile implements node.Controller.
|
||||||
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
proxySpec := proxy.NewSpec()
|
proxySpec := spec.NewSpec()
|
||||||
|
|
||||||
if err := state.GetSpec(proxy.NameProxy, proxySpec); err != nil {
|
if err := state.GetSpec(spec.NameProxy, proxySpec); err != nil {
|
||||||
if errors.Is(err, agent.ErrSpecNotFound) {
|
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||||
logger.Info(ctx, "could not find proxy spec")
|
logger.Info(ctx, "could not find proxy spec")
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ func (c *Controller) stopAllProxies(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) updateProxies(ctx context.Context, spec *proxy.Spec) {
|
func (c *Controller) updateProxies(ctx context.Context, spec *spec.Spec) {
|
||||||
// Stop and remove obsolete proxys
|
// Stop and remove obsolete proxys
|
||||||
for proxyID, entry := range c.proxies {
|
for proxyID, entry := range c.proxies {
|
||||||
if _, exists := spec.Proxies[proxyID]; exists {
|
if _, exists := spec.Proxies[proxyID]; exists {
|
||||||
|
@ -100,7 +100,7 @@ func (c *Controller) updateProxies(ctx context.Context, spec *proxy.Spec) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) updateProxy(ctx context.Context, proxyID proxy.ID, proxySpec proxy.ProxyEntry) (err error) {
|
func (c *Controller) updateProxy(ctx context.Context, proxyID spec.ID, proxySpec spec.ProxyEntry) (err error) {
|
||||||
newProxySpecHash, err := hashstructure.Hash(proxySpec, hashstructure.FormatV2, nil)
|
newProxySpecHash, err := hashstructure.Hash(proxySpec, hashstructure.FormatV2, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
|
@ -140,7 +140,7 @@ func (c *Controller) updateProxy(ctx context.Context, proxyID proxy.ID, proxySpe
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make([]edgeProxy.OptionFunc, 0)
|
options := make([]proxy.OptionFunc, 0)
|
||||||
allowedHosts := make([]string, len(proxySpec.Mappings))
|
allowedHosts := make([]string, len(proxySpec.Mappings))
|
||||||
mappings := make(map[string]*url.URL, len(proxySpec.Mappings))
|
mappings := make(map[string]*url.URL, len(proxySpec.Mappings))
|
||||||
|
|
||||||
|
@ -156,8 +156,8 @@ func (c *Controller) updateProxy(ctx context.Context, proxyID proxy.ID, proxySpe
|
||||||
|
|
||||||
options = append(
|
options = append(
|
||||||
options,
|
options,
|
||||||
edgeProxy.WithAllowedHosts(allowedHosts...),
|
proxy.WithAllowedHosts(allowedHosts...),
|
||||||
edgeProxy.WithRewriteHosts(mappings),
|
proxy.WithRewriteHosts(mappings),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := entry.Proxy.Start(ctx, proxySpec.Address, options...); err != nil {
|
if err := entry.Proxy.Start(ctx, proxySpec.Address, options...); err != nil {
|
||||||
|
@ -173,7 +173,7 @@ func (c *Controller) updateProxy(ctx context.Context, proxyID proxy.ID, proxySpe
|
||||||
|
|
||||||
func NewController() *Controller {
|
func NewController() *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
proxies: make(map[proxy.ID]*proxyEntry),
|
proxies: make(map[spec.ID]*proxyEntry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/proxy"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
"forge.cadoles.com/arcad/edge/pkg/proxy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReverseProxy struct {
|
type ReverseProxy struct {
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/proxy/wildcard"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FilterHosts(allowedHostPatterns ...string) Middleware {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if matches := wildcard.MatchAny(r.Host, allowedHostPatterns...); !matches {
|
||||||
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAllowedHosts(allowedHostPatterns ...string) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Middlewares = append(o.Middlewares, FilterHosts(allowedHostPatterns...))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/proxy/wildcard"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RewriteHosts(mappings map[string]*url.URL) Middleware {
|
||||||
|
patterns := make([]string, len(mappings))
|
||||||
|
|
||||||
|
for p := range mappings {
|
||||||
|
patterns = append(patterns, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(patterns)
|
||||||
|
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
var match *url.URL
|
||||||
|
|
||||||
|
for _, p := range patterns {
|
||||||
|
logger.Debug(ctx, "matching host to pattern", logger.F("host", r.Host), logger.F("pattern", p))
|
||||||
|
|
||||||
|
if matches := wildcard.Match(r.Host, p); !matches {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
match = mappings[p]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if match == nil {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = logger.With(ctx, logger.F("originalHost", r.Host))
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
originalURL := r.URL.String()
|
||||||
|
|
||||||
|
r.URL.Host = match.Host
|
||||||
|
r.URL.Scheme = match.Scheme
|
||||||
|
|
||||||
|
logger.Debug(ctx, "rewriting url", logger.F("from", originalURL), logger.F("to", r.URL.String()))
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRewriteHosts(mappings map[string]*url.URL) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Middlewares = append(o.Middlewares, RewriteHosts(mappings))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Middleware func(h http.Handler) http.Handler
|
||||||
|
|
||||||
|
type ProxyResponseTransformer interface {
|
||||||
|
TransformResponse(*http.Response) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultProxyResponseTransformer struct{}
|
||||||
|
|
||||||
|
// TransformResponse implements ProxyResponseTransformer
|
||||||
|
func (*defaultProxyResponseTransformer) TransformResponse(*http.Response) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ProxyResponseTransformer = &defaultProxyResponseTransformer{}
|
||||||
|
|
||||||
|
type ProxyResponseMiddleware func(ProxyResponseTransformer) ProxyResponseTransformer
|
||||||
|
|
||||||
|
type ProxyRequestTransformer interface {
|
||||||
|
TransformRequest(*http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProxyRequestMiddleware func(ProxyRequestTransformer) ProxyRequestTransformer
|
||||||
|
|
||||||
|
type defaultProxyRequestTransformer struct{}
|
||||||
|
|
||||||
|
// TransformRequest implements ProxyRequestTransformer
|
||||||
|
func (*defaultProxyRequestTransformer) TransformRequest(*http.Request) {}
|
||||||
|
|
||||||
|
var _ ProxyRequestTransformer = &defaultProxyRequestTransformer{}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Middlewares []Middleware
|
||||||
|
ProxyRequestMiddlewares []ProxyRequestMiddleware
|
||||||
|
ProxyResponseMiddlewares []ProxyResponseMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
Middlewares: make([]Middleware, 0),
|
||||||
|
ProxyRequestMiddlewares: make([]ProxyRequestMiddleware, 0),
|
||||||
|
ProxyResponseMiddlewares: make([]ProxyResponseMiddleware, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(*Options)
|
||||||
|
|
||||||
|
func WithProxyRequestMiddlewares(middlewares ...ProxyRequestMiddleware) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.ProxyRequestMiddlewares = middlewares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithproxyResponseMiddlewares(middlewares ...ProxyResponseMiddleware) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.ProxyResponseMiddlewares = middlewares
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Proxy struct {
|
||||||
|
reversers sync.Map
|
||||||
|
handler http.Handler
|
||||||
|
proxyResponseTransformer ProxyResponseTransformer
|
||||||
|
proxyRequestTransformer ProxyRequestTransformer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler
|
||||||
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p.handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) proxyRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
var reverser *httputil.ReverseProxy
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%s://%s", r.URL.Scheme, r.URL.Host)
|
||||||
|
|
||||||
|
createAndStore := func() {
|
||||||
|
target := &url.URL{
|
||||||
|
Scheme: r.URL.Scheme,
|
||||||
|
Host: r.URL.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
reverser = httputil.NewSingleHostReverseProxy(target)
|
||||||
|
|
||||||
|
originalDirector := reverser.Director
|
||||||
|
|
||||||
|
if p.proxyRequestTransformer != nil {
|
||||||
|
reverser.Director = func(r *http.Request) {
|
||||||
|
originalURL := r.URL.String()
|
||||||
|
originalDirector(r)
|
||||||
|
p.proxyRequestTransformer.TransformRequest(r)
|
||||||
|
logger.Debug(ctx, "proxying request", logger.F("targetURL", r.URL.String()), logger.F("originalURL", originalURL))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.proxyResponseTransformer != nil {
|
||||||
|
reverser.ModifyResponse = func(r *http.Response) error {
|
||||||
|
if err := p.proxyResponseTransformer.TransformResponse(r); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.reversers.Store(key, reverser)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, exists := p.reversers.Load(key)
|
||||||
|
if !exists {
|
||||||
|
createAndStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
reverser, ok := raw.(*httputil.ReverseProxy)
|
||||||
|
if !ok {
|
||||||
|
createAndStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
reverser.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(funcs ...OptionFunc) *Proxy {
|
||||||
|
opts := defaultOptions()
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := &Proxy{}
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(proxy.proxyRequest)
|
||||||
|
proxy.handler = createMiddlewareChain(handler, opts.Middlewares)
|
||||||
|
|
||||||
|
proxy.proxyRequestTransformer = createProxyRequestChain(&defaultProxyRequestTransformer{}, opts.ProxyRequestMiddlewares)
|
||||||
|
proxy.proxyResponseTransformer = createProxyResponseChain(&defaultProxyResponseTransformer{}, opts.ProxyResponseMiddlewares)
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ http.Handler = &Proxy{}
|
||||||
|
|
||||||
|
func createMiddlewareChain(handler http.Handler, middlewares []Middleware) http.Handler {
|
||||||
|
reverse(middlewares)
|
||||||
|
|
||||||
|
for _, m := range middlewares {
|
||||||
|
handler = m(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProxyResponseChain(transformer ProxyResponseTransformer, middlewares []ProxyResponseMiddleware) ProxyResponseTransformer {
|
||||||
|
reverse(middlewares)
|
||||||
|
|
||||||
|
for _, m := range middlewares {
|
||||||
|
transformer = m(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProxyRequestChain(transformer ProxyRequestTransformer, middlewares []ProxyRequestMiddleware) ProxyRequestTransformer {
|
||||||
|
reverse(middlewares)
|
||||||
|
|
||||||
|
for _, m := range middlewares {
|
||||||
|
transformer = m(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverse[S ~[]E, E any](s S) {
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package wildcard
|
||||||
|
|
||||||
|
const wildcard = '*'
|
||||||
|
|
||||||
|
func Match(str, pattern string) bool {
|
||||||
|
if pattern == "" {
|
||||||
|
return str == pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
if pattern == string(wildcard) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepMatchRune([]rune(str), []rune(pattern))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchAny(str string, patterns ...string) bool {
|
||||||
|
for _, p := range patterns {
|
||||||
|
if matches := Match(str, p); matches {
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func deepMatchRune(str, pattern []rune) bool {
|
||||||
|
for len(pattern) > 0 {
|
||||||
|
switch pattern[0] {
|
||||||
|
default:
|
||||||
|
if len(str) == 0 || str[0] != pattern[0] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case wildcard:
|
||||||
|
return deepMatchRune(str, pattern[1:]) ||
|
||||||
|
(len(str) > 0 && deepMatchRune(str[1:], pattern))
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str[1:]
|
||||||
|
pattern = pattern[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(str) == 0 && len(pattern) == 0
|
||||||
|
}
|
Loading…
Reference in New Issue