@marcel91200 / caddy Mirror · read-only / commits / 514eef33
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

caddyhttp: Add support to resolve DN in CEL expression (#3608)

v-rosa committed Jul 31, 2020 at 22:06 UTC 514eef33fe6ea4e52438e62f73c8f3c0aadd45db
2 files changed +98 -1
modules/caddyhttp/celmatcher.go
+24
@@ -15,6 +15,7 @@
15 package caddyhttp
16
17 import (
18 + "crypto/x509/pkix"
19 "encoding/json"
20 "fmt"
21 "net/http"
@@ -199,6 +200,27 @@ func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
200 func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
201 func (cr celHTTPRequest) Value() interface{} { return cr }
202
203 +var pkixNameCELType = types.NewTypeValue("pkix.Name", traits.ReceiverType)
204 +
205 +// celPkixName wraps an pkix.Name with
206 +// methods to satisfy the ref.Val interface.
207 +type celPkixName struct{ *pkix.Name }
208 +
209 +func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
210 + return pn.Name, nil
211 +}
212 +func (celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
213 + panic("not implemented")
214 +}
215 +func (pn celPkixName) Equal(other ref.Val) ref.Val {
216 + if o, ok := other.Value().(string); ok {
217 + return types.Bool(pn.Name.String() == o)
218 + }
219 + return types.ValOrErr(other, "%v is not comparable type", other)
220 +}
221 +func (celPkixName) Type() ref.Type { return pkixNameCELType }
222 +func (pn celPkixName) Value() interface{} { return pn }
223 +
224 // celTypeAdapter can adapt our custom types to a CEL value.
225 type celTypeAdapter struct{}
226
@@ -206,6 +228,8 @@ func (celTypeAdapter) NativeToValue(value interface{}) ref.Val {
228 switch v := value.(type) {
229 case celHTTPRequest:
230 return v
231 + case pkix.Name:
232 + return celPkixName{&v}
233 case time.Time:
234 // TODO: eliminate direct protobuf dependency, sigh -- just wrap stdlib time.Time instead...
235 return types.Timestamp{Timestamp: &timestamp.Timestamp{Seconds: v.Unix(), Nanos: int32(v.Nanosecond())}}
modules/caddyhttp/celmatcher_test.go
+74 -1
@@ -15,6 +15,11 @@
15 package caddyhttp
16
17 import (
18 + "context"
19 + "crypto/tls"
20 + "crypto/x509"
21 + "encoding/pem"
22 + "net/http/httptest"
23 "testing"
24
25 "github.com/caddyserver/caddy/v2"
@@ -27,7 +32,7 @@ func TestMatchExpressionProvision(t *testing.T) {
32 wantErr bool
33 }{
34 {
30 - name: "boolean mtaches succeed",
35 + name: "boolean matches succeed",
36 expression: &MatchExpression{
37 Expr: "{http.request.uri.query} != ''",
38 },
@@ -49,3 +54,71 @@ func TestMatchExpressionProvision(t *testing.T) {
54 })
55 }
56 }
57 +
58 +func TestMatchExpressionMatch(t *testing.T) {
59 +
60 + clientCert := []byte(`-----BEGIN CERTIFICATE-----
61 +MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
62 +eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
63 +A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
64 +iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
65 +z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
66 +fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
67 +BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
68 +AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
69 +eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
70 +3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
71 +9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
72 +-----END CERTIFICATE-----`)
73 +
74 + tests := []struct {
75 + name string
76 + expression *MatchExpression
77 + wantErr bool
78 + wantResult bool
79 + clientCertificate []byte
80 + }{
81 + {
82 + name: "boolean matches succeed for placeholder http.request.tls.client.subject",
83 + expression: &MatchExpression{
84 + Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'",
85 + },
86 + clientCertificate: clientCert,
87 + wantResult: true,
88 + },
89 + }
90 + for _, tt := range tests {
91 + t.Run(tt.name, func(t *testing.T) {
92 + if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr {
93 + t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr)
94 + }
95 +
96 + req := httptest.NewRequest("GET", "https://example.com/foo", nil)
97 + repl := caddy.NewReplacer()
98 + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
99 + req = req.WithContext(ctx)
100 + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
101 +
102 + if tt.clientCertificate != nil {
103 + block, _ := pem.Decode(clientCert)
104 + if block == nil {
105 + t.Fatalf("failed to decode PEM certificate")
106 + }
107 +
108 + cert, err := x509.ParseCertificate(block.Bytes)
109 + if err != nil {
110 + t.Fatalf("failed to decode PEM certificate: %v", err)
111 + }
112 +
113 + req.TLS = &tls.ConnectionState{
114 + PeerCertificates: []*x509.Certificate{cert},
115 + }
116 + }
117 +
118 + if tt.expression.Match(req) != tt.wantResult {
119 + t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tt.wantResult, tt.expression)
120 + }
121 +
122 + })
123 + }
124 +}