From 105acfa08664c97460a6fe3fb49635618be5bcb2 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 30 Mar 2020 11:49:53 -0600 Subject: Keep type information with placeholders until replacements happen --- modules/caddyhttp/celmatcher.go | 32 ++++++++++++++- modules/caddyhttp/replacer.go | 54 +++++++++----------------- modules/caddyhttp/reverseproxy/reverseproxy.go | 7 ++-- modules/caddyhttp/rewrite/rewrite.go | 17 +++++++- modules/caddyhttp/server.go | 11 +++--- 5 files changed, 71 insertions(+), 50 deletions(-) (limited to 'modules/caddyhttp') diff --git a/modules/caddyhttp/celmatcher.go b/modules/caddyhttp/celmatcher.go index a78bd9c..84565e4 100644 --- a/modules/caddyhttp/celmatcher.go +++ b/modules/caddyhttp/celmatcher.go @@ -86,7 +86,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error { decls.NewFunction(placeholderFuncName, decls.NewOverload(placeholderFuncName+"_httpRequest_string", []*exprpb.Type{httpRequestObjectType, decls.String}, - decls.String)), + decls.Any)), ), cel.CustomTypeAdapter(celHTTPRequestTypeAdapter{}), ext.Strings(), @@ -210,7 +210,35 @@ func caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val { repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) val, _ := repl.Get(string(phStr)) - return types.String(val) + // TODO: this is... kinda awful and underwhelming, how can we expand CEL's type system more easily? + switch v := val.(type) { + case string: + return types.String(v) + case fmt.Stringer: + return types.String(v.String()) + case error: + return types.NewErr(v.Error()) + case int: + return types.Int(v) + case int32: + return types.Int(v) + case int64: + return types.Int(v) + case uint: + return types.Int(v) + case uint32: + return types.Int(v) + case uint64: + return types.Int(v) + case float32: + return types.Double(v) + case float64: + return types.Double(v) + case bool: + return types.Bool(v) + default: + return types.String(fmt.Sprintf("%+v", v)) + } } // Interface guards diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index c9c7522..f55bb0a 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -31,7 +31,7 @@ import ( ) func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) { - httpVars := func(key string) (string, bool) { + httpVars := func(key string) (interface{}, bool) { if req != nil { // query string parameters if strings.HasPrefix(key, reqURIQueryReplPrefix) { @@ -62,7 +62,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo } } - // http.request.tls. + // http.request.tls.* if strings.HasPrefix(key, reqTLSReplPrefix) { return getReqTLSReplacement(req, key) } @@ -182,21 +182,10 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo if strings.HasPrefix(key, varsReplPrefix) { varName := key[len(varsReplPrefix):] tbl := req.Context().Value(VarsCtxKey).(map[string]interface{}) - raw, ok := tbl[varName] - if !ok { - // variables can be dynamic, so always return true - // even when it may not be set; treat as empty - return "", true - } - // do our best to convert it to a string efficiently - switch val := raw.(type) { - case string: - return val, true - case fmt.Stringer: - return val.String(), true - default: - return fmt.Sprintf("%s", val), true - } + raw, _ := tbl[varName] + // variables can be dynamic, so always return true + // even when it may not be set; treat as empty then + return raw, true } } @@ -211,19 +200,19 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo } } - return "", false + return nil, false } repl.Map(httpVars) } -func getReqTLSReplacement(req *http.Request, key string) (string, bool) { +func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) { if req == nil || req.TLS == nil { - return "", false + return nil, false } if len(key) < len(reqTLSReplPrefix) { - return "", false + return nil, false } field := strings.ToLower(key[len(reqTLSReplPrefix):]) @@ -231,20 +220,20 @@ func getReqTLSReplacement(req *http.Request, key string) (string, bool) { if strings.HasPrefix(field, "client.") { cert := getTLSPeerCert(req.TLS) if cert == nil { - return "", false + return nil, false } switch field { case "client.fingerprint": return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true case "client.issuer": - return cert.Issuer.String(), true + return cert.Issuer, true case "client.serial": - return fmt.Sprintf("%x", cert.SerialNumber), true + return cert.SerialNumber, true case "client.subject": - return cert.Subject.String(), true + return cert.Subject, true default: - return "", false + return nil, false } } @@ -254,22 +243,15 @@ func getReqTLSReplacement(req *http.Request, key string) (string, bool) { case "cipher_suite": return tls.CipherSuiteName(req.TLS.CipherSuite), true case "resumed": - if req.TLS.DidResume { - return "true", true - } - return "false", true + return req.TLS.DidResume, true case "proto": return req.TLS.NegotiatedProtocol, true case "proto_mutual": - if req.TLS.NegotiatedProtocolIsMutual { - return "true", true - } - return "false", true + return req.TLS.NegotiatedProtocolIsMutual, true case "server_name": return req.TLS.ServerName, true - default: - return "", false } + return nil, false } // getTLSPeerCert retrieves the first peer certificate from a TLS session. diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 4ac50ac..6d0d441 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -24,7 +24,6 @@ import ( "net" "net/http" "regexp" - "strconv" "strings" "sync" "time" @@ -328,9 +327,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address) repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host) repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port) - repl.Set("http.reverse_proxy.upstream.requests", strconv.Itoa(upstream.Host.NumRequests())) - repl.Set("http.reverse_proxy.upstream.max_requests", strconv.Itoa(upstream.MaxRequests)) - repl.Set("http.reverse_proxy.upstream.fails", strconv.Itoa(upstream.Host.Fails())) + repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests()) + repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests) + repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails()) // mutate request headers according to this upstream; // because we're in a retry loop, we have to copy diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go index ad05486..3ba63c4 100644 --- a/modules/caddyhttp/rewrite/rewrite.go +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -15,8 +15,10 @@ package rewrite import ( + "fmt" "net/http" "net/url" + "strconv" "strings" "github.com/caddyserver/caddy/v2" @@ -208,11 +210,22 @@ func buildQueryString(qs string, repl *caddy.Replacer) string { // consume the component and write the result comp := qs[:end] - comp, _ = repl.ReplaceFunc(comp, func(name, val string) (string, error) { + comp, _ = repl.ReplaceFunc(comp, func(name string, val interface{}) (interface{}, error) { if name == "http.request.uri.query" && wroteVal { return val, nil // already escaped } - return url.QueryEscape(val), nil + var valStr string + switch v := val.(type) { + case string: + valStr = v + case fmt.Stringer: + valStr = v.String() + case int: + valStr = strconv.Itoa(v) + default: + valStr = fmt.Sprintf("%+v", v) + } + return url.QueryEscape(valStr), nil }) if end < len(qs) { end++ // consume delimiter diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index c7780b0..72a67a7 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -21,7 +21,6 @@ import ( "net" "net/http" "net/url" - "strconv" "strings" "time" @@ -166,9 +165,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer func() { latency := time.Since(start) - repl.Set("http.response.status", strconv.Itoa(wrec.Status())) - repl.Set("http.response.size", strconv.Itoa(wrec.Size())) - repl.Set("http.response.latency", latency.String()) + repl.Set("http.response.status", wrec.Status()) + repl.Set("http.response.size", wrec.Size()) + repl.Set("http.response.latency", latency) logger := accLog if s.Logs != nil && s.Logs.LoggerNames != nil { @@ -360,9 +359,9 @@ func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request { // add error values to the replacer repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - repl.Set("http.error", err.Error()) + repl.Set("http.error", err) if handlerErr, ok := err.(HandlerError); ok { - repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode)) + repl.Set("http.error.status_code", handlerErr.StatusCode) repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode)) repl.Set("http.error.trace", handlerErr.Trace) repl.Set("http.error.id", handlerErr.ID) -- cgit v1.2.3