feat(rewriter): add redirect(), get_cookie(), add_cookie() methods to rule engine (#36)
Some checks are pending
Cadoles/bouncer/pipeline/pr-develop Build started...
Some checks are pending
Cadoles/bouncer/pipeline/pr-develop Build started...
This commit is contained in:
79
internal/proxy/director/layer/rewriter/api.go
Normal file
79
internal/proxy/director/layer/rewriter/api.go
Normal file
@ -0,0 +1,79 @@
|
||||
package rewriter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type errRedirect struct {
|
||||
statusCode int
|
||||
url string
|
||||
}
|
||||
|
||||
func (e *errRedirect) StatusCode() int {
|
||||
return e.statusCode
|
||||
}
|
||||
|
||||
func (e *errRedirect) URL() string {
|
||||
return e.url
|
||||
}
|
||||
|
||||
func (e *errRedirect) Error() string {
|
||||
return fmt.Sprintf("redirect %d %s", e.statusCode, e.url)
|
||||
}
|
||||
|
||||
func newErrRedirect(statusCode int, url string) *errRedirect {
|
||||
return &errRedirect{
|
||||
url: url,
|
||||
statusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
var _ error = &errRedirect{}
|
||||
|
||||
func redirectFunc() expr.Option {
|
||||
return expr.Function(
|
||||
"redirect",
|
||||
func(params ...any) (any, error) {
|
||||
_, err := rule.Assert[context.Context](params[0])
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
statusCode, err := rule.Assert[int](params[1])
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if statusCode < 300 || statusCode >= 400 {
|
||||
return nil, errors.Errorf("unexpected redirect status code '%d'", statusCode)
|
||||
}
|
||||
|
||||
url, err := rule.Assert[string](params[2])
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil, newErrRedirect(statusCode, url)
|
||||
},
|
||||
new(func(context.Context, int, string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func WithRewriterFuncs() rule.OptionFunc {
|
||||
return func(opts *rule.Options) {
|
||||
funcs := []expr.Option{
|
||||
redirectFunc(),
|
||||
}
|
||||
|
||||
if len(opts.Expr) == 0 {
|
||||
opts.Expr = make([]expr.Option, 0)
|
||||
}
|
||||
|
||||
opts.Expr = append(opts.Expr, funcs...)
|
||||
}
|
||||
}
|
@ -46,6 +46,12 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||
}
|
||||
|
||||
if err := l.applyRequestRules(ctx, r, layer.Revision, options); err != nil {
|
||||
var redirect *errRedirect
|
||||
if errors.As(err, &redirect) {
|
||||
http.Redirect(w, r, redirect.URL(), redirect.StatusCode())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not apply request rules", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
@ -88,6 +94,7 @@ func New(funcs ...OptionFunc) *Layer {
|
||||
engine, err := rule.NewEngine[*RequestVars](
|
||||
rule.WithRules(options.Rules.Request...),
|
||||
ruleHTTP.WithRequestFuncs(),
|
||||
WithRewriterFuncs(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
|
@ -3,6 +3,7 @@ package rewriter
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||
@ -27,6 +28,26 @@ type URLVar struct {
|
||||
RawFragment string `expr:"raw_fragment"`
|
||||
}
|
||||
|
||||
func fromURL(url *url.URL) URLVar {
|
||||
return URLVar{
|
||||
Scheme: url.Scheme,
|
||||
Opaque: url.Opaque,
|
||||
User: UserVar{
|
||||
Username: url.User.Username(),
|
||||
Password: func() string {
|
||||
passwd, _ := url.User.Password()
|
||||
return passwd
|
||||
}(),
|
||||
},
|
||||
Host: url.Host,
|
||||
Path: url.Path,
|
||||
RawPath: url.RawPath,
|
||||
RawQuery: url.RawQuery,
|
||||
Fragment: url.Fragment,
|
||||
RawFragment: url.RawFragment,
|
||||
}
|
||||
}
|
||||
|
||||
type UserVar struct {
|
||||
Username string `expr:"username"`
|
||||
Password string `expr:"password"`
|
||||
@ -48,6 +69,24 @@ type RequestVar struct {
|
||||
RequestURI string `expr:"request_uri"`
|
||||
}
|
||||
|
||||
func fromRequest(r *http.Request) RequestVar {
|
||||
return RequestVar{
|
||||
Method: r.Method,
|
||||
URL: fromURL(r.URL),
|
||||
RawURL: r.URL.String(),
|
||||
Proto: r.Proto,
|
||||
ProtoMajor: r.ProtoMajor,
|
||||
ProtoMinor: r.ProtoMinor,
|
||||
Header: r.Header,
|
||||
ContentLength: r.ContentLength,
|
||||
TransferEncoding: r.TransferEncoding,
|
||||
Host: r.Host,
|
||||
Trailer: r.Trailer,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
RequestURI: r.RequestURI,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRevision int, options *LayerOptions) error {
|
||||
rules := options.Rules.Request
|
||||
if len(rules) == 0 {
|
||||
@ -65,54 +104,8 @@ func (l *Layer) applyRequestRules(ctx context.Context, r *http.Request, layerRev
|
||||
}
|
||||
|
||||
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,
|
||||
URL: URLVar{
|
||||
Scheme: r.URL.Scheme,
|
||||
Opaque: r.URL.Opaque,
|
||||
User: UserVar{
|
||||
Username: r.URL.User.Username(),
|
||||
Password: func() string {
|
||||
passwd, _ := r.URL.User.Password()
|
||||
return passwd
|
||||
}(),
|
||||
},
|
||||
Host: r.URL.Host,
|
||||
Path: r.URL.Path,
|
||||
RawPath: r.URL.RawPath,
|
||||
RawQuery: r.URL.RawQuery,
|
||||
Fragment: r.URL.Fragment,
|
||||
RawFragment: r.URL.RawFragment,
|
||||
},
|
||||
RawURL: r.URL.String(),
|
||||
Proto: r.Proto,
|
||||
ProtoMajor: r.ProtoMajor,
|
||||
ProtoMinor: r.ProtoMinor,
|
||||
Header: r.Header,
|
||||
ContentLength: r.ContentLength,
|
||||
TransferEncoding: r.TransferEncoding,
|
||||
Host: r.Host,
|
||||
Trailer: r.Trailer,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
RequestURI: r.RequestURI,
|
||||
},
|
||||
OriginalURL: fromURL(originalURL),
|
||||
Request: fromRequest(r),
|
||||
}
|
||||
|
||||
ctx = ruleHTTP.WithRequest(ctx, r)
|
||||
@ -169,54 +162,8 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||
}
|
||||
|
||||
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,
|
||||
URL: URLVar{
|
||||
Scheme: r.Request.URL.Scheme,
|
||||
Opaque: r.Request.URL.Opaque,
|
||||
User: UserVar{
|
||||
Username: r.Request.URL.User.Username(),
|
||||
Password: func() string {
|
||||
passwd, _ := r.Request.URL.User.Password()
|
||||
return passwd
|
||||
}(),
|
||||
},
|
||||
Host: r.Request.URL.Host,
|
||||
Path: r.Request.URL.Path,
|
||||
RawPath: r.Request.URL.RawPath,
|
||||
RawQuery: r.Request.URL.RawQuery,
|
||||
Fragment: r.Request.URL.Fragment,
|
||||
RawFragment: r.Request.URL.RawFragment,
|
||||
},
|
||||
RawURL: r.Request.URL.String(),
|
||||
Proto: r.Request.Proto,
|
||||
ProtoMajor: r.Request.ProtoMajor,
|
||||
ProtoMinor: r.Request.ProtoMinor,
|
||||
Header: r.Request.Header,
|
||||
ContentLength: r.Request.ContentLength,
|
||||
TransferEncoding: r.Request.TransferEncoding,
|
||||
Host: r.Request.Host,
|
||||
Trailer: r.Request.Trailer,
|
||||
RemoteAddr: r.Request.RemoteAddr,
|
||||
RequestURI: r.Request.RequestURI,
|
||||
},
|
||||
OriginalURL: fromURL(originalURL),
|
||||
Request: fromRequest(r.Request),
|
||||
Response: ResponseVar{
|
||||
Proto: r.Proto,
|
||||
ProtoMajor: r.ProtoMajor,
|
||||
@ -231,6 +178,7 @@ func (l *Layer) applyResponseRules(ctx context.Context, r *http.Response, layerR
|
||||
}
|
||||
|
||||
ctx = ruleHTTP.WithResponse(ctx, r)
|
||||
ctx = ruleHTTP.WithRequest(ctx, r.Request)
|
||||
|
||||
if _, err := engine.Apply(ctx, vars); err != nil {
|
||||
return errors.WithStack(err)
|
||||
|
Reference in New Issue
Block a user