@marcel91200 / caddy Mirror · read-only / commits / c4472363
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: Fix default SNI for default conn policy (#3141)

* add integration tests * removed SNI test * remove integration test condition * minor edit * fix sni when using static certificates Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

Mark Sargent committed Mar 14, 2020 at 06:32 UTC c4472363579e26485fdfc01f4fd79b86d44acd86
7 files changed +458 -2
azure-pipelines.yml
+9
@@ -130,6 +130,15 @@ jobs:
130 continueOnError: true
131 displayName: Run tests
132
133 + - script: |
134 + set -e
135 + cmd/caddy/caddy start
136 + go test -v -count=1 ./caddytest/...
137 + cmd/caddy/caddy stop
138 + workingDirectory: '$(modulePath)'
139 + continueOnError: false
140 + displayName: Run Integration tests
141 +
142 - script: |
143 mkdir coverage
144 gocov convert cover-profile.out > coverage/coverage.json
caddyconfig/httpcaddyfile/httptype.go
+3 -2
@@ -450,6 +450,8 @@ func (st *ServerType) serversFromPairings(
450 ) (map[string]*caddyhttp.Server, error) {
451 servers := make(map[string]*caddyhttp.Server)
452
453 + defaultSNI := tryString(options["default_sni"], warnings)
454 +
455 for i, p := range pairings {
456 srv := &caddyhttp.Server{
457 Listen: p.addresses,
@@ -499,7 +501,6 @@ func (st *ServerType) serversFromPairings(
501 }
502
503 // tls: connection policies and toggle auto HTTPS
502 - defaultSNI := tryString(options["default_sni"], warnings)
504 if _, ok := sblock.pile["tls.off"]; ok {
505 // TODO: right now, no directives yield any tls.off value...
506 // tls off: disable TLS (and automatic HTTPS) for server block's names
@@ -614,7 +615,7 @@ func (st *ServerType) serversFromPairings(
615 // important that it goes at the end) - see issue #3004:
616 // https://github.com/caddyserver/caddy/issues/3004
617 if len(srv.TLSConnPolicies) > 0 && !hasCatchAllTLSConnPolicy {
617 - srv.TLSConnPolicies = append(srv.TLSConnPolicies, new(caddytls.ConnectionPolicy))
618 + srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI})
619 }
620
621 srv.Routes = consolidateRoutes(srv.Routes)
caddytest/caddy.localhost.crt new
+23
@@ -0,0 +1,23 @@
1 +-----BEGIN CERTIFICATE-----
2 +MIID5zCCAs8CFFmAAFKV79uhzxc5qXbUw3oBNsYXMA0GCSqGSIb3DQEBCwUAMIGv
3 +MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs
4 +b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBEq
5 +LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw
6 +HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMDIwODAxMTZa
7 +Fw0zMDAyMjgwODAxMTZaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ
8 +BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv
9 +cGVtZW50MRowGAYDVQQDDBEqLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j
10 +YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh
11 +bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJngfeirQkWaU8ihgIC5
12 +SKpRQX/3koRjljDK/oCbhLs+wg592kIwVv06l7+mn7NSaNBloabjuA1GqyLRsNLL
13 +ptrv0HvXa5qLx28+icsb2Ny3dJnQaj9w9PwjxQ1qZqEJfWRH1D8Vz9AmB+QSV/Gu
14 +8e8alGFewlYZVfH1kbxoTT6QorF37TeA3bh1fgKFtzsGYKswcaZNdDBBHzLunCKZ
15 +HU6U6L45hm+yLADj3mmDLafUeiVOt6MRLLoSD1eLRVSXGrNo+brJ87zkZntI9+W1
16 +JxOBoXtZCwka7k2DlAtLihsrmBZA2ZC9yVeu/SQy3qb3iCNnTFTCyAnWeTCr6Tcq
17 +6w8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAOWfXqpAmD4C3wGiMeZAeaaS4hDAR
18 ++JmN+avPDA6F6Bq7DB4NJuIwVUlaDL2s07w5VJJtW52aZVKoBlgHR5yG/XUli6J7
19 +YUJRmdQJvHUSu26cmKvyoOaTrEYbmvtGICWtZc8uTlMf9wQZbJA4KyxTgEQJDXsZ
20 +B2XFe+wVdhAgEpobYDROi+l/p8TL5z3U24LpwVTcJy5sEZVv7Wfs886IyxU8ORt8
21 +VZNcDiH6V53OIGeiufIhia/mPe6jbLntfGZfIFxtCcow4IA/lTy1ned7K5fmvNNb
22 +ZilxOQUk+wVK8genjdrZVAnAxsYLHJIb5yf9O7rr6fWciVMF3a0k5uNK1w==
23 +-----END CERTIFICATE-----
caddytest/caddy.localhost.key new
+27
@@ -0,0 +1,27 @@
1 +-----BEGIN RSA PRIVATE KEY-----
2 +MIIEogIBAAKCAQEAmeB96KtCRZpTyKGAgLlIqlFBf/eShGOWMMr+gJuEuz7CDn3a
3 +QjBW/TqXv6afs1Jo0GWhpuO4DUarItGw0sum2u/Qe9drmovHbz6JyxvY3Ld0mdBq
4 +P3D0/CPFDWpmoQl9ZEfUPxXP0CYH5BJX8a7x7xqUYV7CVhlV8fWRvGhNPpCisXft
5 +N4DduHV+AoW3OwZgqzBxpk10MEEfMu6cIpkdTpTovjmGb7IsAOPeaYMtp9R6JU63
6 +oxEsuhIPV4tFVJcas2j5usnzvORme0j35bUnE4Ghe1kLCRruTYOUC0uKGyuYFkDZ
7 +kL3JV679JDLepveII2dMVMLICdZ5MKvpNyrrDwIDAQABAoIBAFcPK01zb6hfm12c
8 ++k5aBiHOnUdgc/YRPg1XHEz5MEycQkDetZjTLrRQ7UBSbnKPgpu9lIsOtbhVLkgh
9 +6XAqJroiCou2oruqr+hhsqZGmBiwdvj7cNF6ADGTr05az7v22YneFdinZ481pStF
10 +sZocx+bm2+KHMV5zMSwXKyA0xtdJLxs2yklniDBxSZRppgppq1pDPprP5DkgKPfe
11 +3ekUmbQd5bHmivhW8ItbJLuf82XSsMBZ9ZhKiKIlWlbKAgiSV3SqnUQb5fi7l8hG
12 +yYZxbuCUIGFwKmEpUBBt/nyxrOlMiNtDh9JhrPmijTV3slq70pCLwLL/Ai2aeear
13 +EVA5VhkCgYEAyAmxfPqc2P7BsDAp67/sA7OEPso9qM4WyuWiVdlX2gb9TLNLYbPX
14 +Kk/UmpAIVzpoTAGY5Zp3wkvdD/ou8uUQsE8ioNn4S1a4G9XURH1wVhcEbUiAKI1S
15 +QVBH9B/Pj3eIp5OTKwob0Wj7DNdxoH7ed/Eok0EaTWzOA8pCWADKv/MCgYEAxOzY
16 +YsX7Nl+eyZr2+9unKyeAK/D1DCT/o99UUAHx72/xaBVP/06cfzpvKBNcF9iYc+fq
17 +R1yIUIrDRoSmYKBq+Kb3+nOg1nrqih/NBTokbTiI4Q+/30OQt0Al1e7y9iNKqV8H
18 +jYZItzluGNrWKedZbATwBwbVCY2jnNl6RMDnS3UCgYBxj3cwQUHLuoyQjjcuO80r
19 +qLzZvIxWiXDNDKIk5HcIMlGYOmz/8U2kGp/SgxQJGQJeq8V2C0QTjGfaCyieAcaA
20 +oNxCvptDgd6RBsoze5bLeNOtiqwe2WOp6n5+q5R0mOJ+Z7vzghCayGNFPgWmnH+F
21 +TeW/+wSIkc0+v5L8TK7NWwKBgBrlWlyLO9deUfqpHqihhICBYaEexOlGuF+yZfqT
22 +eW7BdFBJ8OYm33sFCR+JHV/oZlIWT8o1Wizd9vPPtEWoQ1P4wg/D8Si6GwSIeWEI
23 +YudD/HX4x7T/rmlI6qIAg9CYW18sqoRq3c2gm2fro6qPfYgiWIItLbWjUcBfd7Ki
24 +QjTtAoGARKdRv3jMWL84rlEx1nBRgL3pe9Dt+Uxzde2xT3ZeF+5Hp9NfU01qE6M6
25 +1I6H64smqpetlsXmCEVKwBemP3pJa6avLKgIYiQvHAD/v4rs9mqgy1RTqtYyGNhR
26 +1A/6dKkbiZ6wzePLLPasXVZxSKEviXf5gJooqumQVSVhCswyCZ0=
27 +-----END RSA PRIVATE KEY-----
caddytest/caddytest.go new
+272
@@ -0,0 +1,272 @@
1 +package caddytest
2 +
3 +import (
4 + "bytes"
5 + "context"
6 + "crypto/tls"
7 + "encoding/json"
8 + "errors"
9 + "fmt"
10 + "io/ioutil"
11 + "log"
12 + "net"
13 + "net/http"
14 + "os"
15 + "path"
16 + "regexp"
17 + "runtime"
18 + "strings"
19 + "testing"
20 + "time"
21 +)
22 +
23 +// Defaults store any configuration required to make the tests run
24 +type Defaults struct {
25 + // Port we expect caddy to listening on
26 + AdminPort int
27 + // Certificates we expect to be loaded before attempting to run the tests
28 + Certifcates []string
29 +}
30 +
31 +// Default testing values
32 +var Default = Defaults{
33 + AdminPort: 2019,
34 + Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
35 +}
36 +
37 +var (
38 + matchKey = regexp.MustCompile(`(/[\w\d\.]+\.key)`)
39 + matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
40 +)
41 +
42 +type configLoadError struct {
43 + Response string
44 +}
45 +
46 +func (e configLoadError) Error() string { return e.Response }
47 +
48 +// InitServer this will configure the server with a configurion of a specific
49 +// type. The configType must be either "json" or the adapter type.
50 +func InitServer(t *testing.T, rawConfig string, configType string) {
51 + if err := initServer(t, rawConfig, configType); errors.Is(err, &configLoadError{}) {
52 + t.Logf("failed to load config: %s", err)
53 + t.Fail()
54 + }
55 +}
56 +
57 +// InitServer this will configure the server with a configurion of a specific
58 +// type. The configType must be either "json" or the adapter type.
59 +func initServer(t *testing.T, rawConfig string, configType string) error {
60 +
61 + err := validateTestPrerequisites()
62 + if err != nil {
63 + t.Skipf("skipping tests as failed integration prerequisites. %s", err)
64 + return nil
65 + }
66 +
67 + t.Cleanup(func() {
68 + if t.Failed() {
69 + res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
70 + if err != nil {
71 + t.Log("unable to read the current config")
72 + }
73 + defer res.Body.Close()
74 + body, err := ioutil.ReadAll(res.Body)
75 +
76 + var out bytes.Buffer
77 + json.Indent(&out, body, "", " ")
78 + t.Logf("----------- failed with config -----------\n%s", out.String())
79 + }
80 + })
81 +
82 + rawConfig = prependCaddyFilePath(rawConfig)
83 + client := &http.Client{
84 + Timeout: time.Second * 2,
85 + }
86 + req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
87 + if err != nil {
88 + t.Errorf("failed to create request. %s", err)
89 + return err
90 + }
91 +
92 + if configType == "json" {
93 + req.Header.Add("Content-Type", "application/json")
94 + } else {
95 + req.Header.Add("Content-Type", "text/"+configType)
96 + }
97 +
98 + res, err := client.Do(req)
99 + if err != nil {
100 + t.Errorf("unable to contact caddy server. %s", err)
101 + return err
102 + }
103 + defer res.Body.Close()
104 + body, err := ioutil.ReadAll(res.Body)
105 + if err != nil {
106 + t.Errorf("unable to read response. %s", err)
107 + return err
108 + }
109 +
110 + if res.StatusCode != 200 {
111 + return configLoadError{Response: string(body)}
112 + }
113 +
114 + return nil
115 +}
116 +
117 +var hasValidated bool
118 +var arePrerequisitesValid bool
119 +
120 +func validateTestPrerequisites() error {
121 +
122 + if hasValidated {
123 + if !arePrerequisitesValid {
124 + return errors.New("caddy integration prerequisites failed. see first error")
125 + }
126 + return nil
127 + }
128 +
129 + hasValidated = true
130 + arePrerequisitesValid = false
131 +
132 + // check certificates are found
133 + for _, certName := range Default.Certifcates {
134 + if _, err := os.Stat(getIntegrationDir() + certName); os.IsNotExist(err) {
135 + return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
136 + }
137 + }
138 +
139 + // assert that caddy is running
140 + client := &http.Client{
141 + Timeout: time.Second * 2,
142 + }
143 + _, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
144 + if err != nil {
145 + return errors.New("caddy integration test caddy server not running. Expected to be listening on localhost:2019")
146 + }
147 +
148 + arePrerequisitesValid = true
149 + return nil
150 +}
151 +
152 +func getIntegrationDir() string {
153 +
154 + _, filename, _, ok := runtime.Caller(1)
155 + if !ok {
156 + panic("unable to determine the current file path")
157 + }
158 +
159 + return path.Dir(filename)
160 +}
161 +
162 +// use the convention to replace /[certificatename].[crt|key] with the full path
163 +// this helps reduce the noise in test configurations and also allow this
164 +// to run in any path
165 +func prependCaddyFilePath(rawConfig string) string {
166 + r := matchKey.ReplaceAllString(rawConfig, getIntegrationDir()+"$1")
167 + r = matchCert.ReplaceAllString(r, getIntegrationDir()+"$1")
168 + return r
169 +}
170 +
171 +// creates a testing transport that forces call dialing connections to happen locally
172 +func createTestingTransport() *http.Transport {
173 +
174 + dialer := net.Dialer{
175 + Timeout: 5 * time.Second,
176 + KeepAlive: 5 * time.Second,
177 + DualStack: true,
178 + }
179 +
180 + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
181 + parts := strings.Split(addr, ":")
182 + destAddr := fmt.Sprintf("127.0.0.1:%s", parts[1])
183 + log.Printf("caddytest: redirecting the dialer from %s to %s", addr, destAddr)
184 + return dialer.DialContext(ctx, network, destAddr)
185 + }
186 +
187 + return &http.Transport{
188 + Proxy: http.ProxyFromEnvironment,
189 + DialContext: dialContext,
190 + ForceAttemptHTTP2: true,
191 + MaxIdleConns: 100,
192 + IdleConnTimeout: 90 * time.Second,
193 + TLSHandshakeTimeout: 5 * time.Second,
194 + ExpectContinueTimeout: 1 * time.Second,
195 + TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
196 + }
197 +}
198 +
199 +// AssertLoadError will load a config and expect an error
200 +func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
201 + err := initServer(t, rawConfig, configType)
202 + if !strings.Contains(err.Error(), expectedError) {
203 + t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
204 + }
205 +}
206 +
207 +// AssertGetResponse request a URI and assert the status code and the body contains a string
208 +func AssertGetResponse(t *testing.T, requestURI string, statusCode int, expectedBody string) (*http.Response, string) {
209 + resp, body := AssertGetResponseBody(t, requestURI, statusCode)
210 + if !strings.Contains(body, expectedBody) {
211 + t.Errorf("expected response body \"%s\" but got \"%s\"", expectedBody, body)
212 + }
213 + return resp, string(body)
214 +}
215 +
216 +// AssertGetResponseBody request a URI and assert the status code matches
217 +func AssertGetResponseBody(t *testing.T, requestURI string, expectedStatusCode int) (*http.Response, string) {
218 +
219 + client := &http.Client{
220 + Transport: createTestingTransport(),
221 + }
222 +
223 + resp, err := client.Get(requestURI)
224 + if err != nil {
225 + t.Errorf("failed to call server %s", err)
226 + return nil, ""
227 + }
228 +
229 + defer resp.Body.Close()
230 +
231 + if expectedStatusCode != resp.StatusCode {
232 + t.Errorf("expected status code: %d but got %d", expectedStatusCode, resp.StatusCode)
233 + }
234 +
235 + body, err := ioutil.ReadAll(resp.Body)
236 + if err != nil {
237 + t.Errorf("unable to read the response body %s", err)
238 + return nil, ""
239 + }
240 +
241 + return resp, string(body)
242 +}
243 +
244 +// AssertRedirect makes a request and asserts the redirection happens
245 +func AssertRedirect(t *testing.T, requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
246 +
247 + redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
248 + return http.ErrUseLastResponse
249 + }
250 +
251 + client := &http.Client{
252 + CheckRedirect: redirectPolicyFunc,
253 + Transport: createTestingTransport(),
254 + }
255 +
256 + resp, err := client.Get(requestURI)
257 + if err != nil {
258 + t.Errorf("failed to call server %s", err)
259 + return nil
260 + }
261 +
262 + if expectedStatusCode != resp.StatusCode {
263 + t.Errorf("expected status code: %d but got %d", expectedStatusCode, resp.StatusCode)
264 + }
265 +
266 + loc, err := resp.Location()
267 + if expectedToLocation != loc.String() {
268 + t.Errorf("expected location: \"%s\" but got \"%s\"", expectedToLocation, loc.String())
269 + }
270 +
271 + return resp
272 +}
caddytest/caddytest_test.go new
+33
@@ -0,0 +1,33 @@
1 +package caddytest
2 +
3 +import (
4 + "strings"
5 + "testing"
6 +)
7 +
8 +func TestReplaceCertificatePaths(t *testing.T) {
9 + rawConfig := `a.caddy.localhost:9443 {
10 + tls /caddy.localhost.crt /caddy.localhost.key {
11 + }
12 +
13 + redir / https://b.caddy.localhost:9443/version 301
14 +
15 + respond /version 200 {
16 + body "hello from a.caddy.localhost"
17 + }
18 + }`
19 +
20 + r := prependCaddyFilePath(rawConfig)
21 +
22 + if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.crt") {
23 + t.Error("expected the /caddy.localhost.crt to be expanded to include the full path")
24 + }
25 +
26 + if !strings.Contains(r, getIntegrationDir()+"/caddy.localhost.key") {
27 + t.Error("expected the /caddy.localhost.crt to be expanded to include the full path")
28 + }
29 +
30 + if !strings.Contains(r, "https://b.caddy.localhost:9443/version") {
31 + t.Error("expected redirect uri to be unchanged")
32 + }
33 +}
caddytest/integration/caddyfile_test.go new
+91
@@ -0,0 +1,91 @@
1 +package integration
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/caddyserver/caddy/v2/caddytest"
7 +)
8 +
9 +func TestRespond(t *testing.T) {
10 +
11 + // arrange
12 + caddytest.InitServer(t, `
13 + {
14 + http_port 9080
15 + https_port 9443
16 + }
17 +
18 + localhost:9080 {
19 + respond /version 200 {
20 + body "hello from a.caddy.localhost"
21 + }
22 + }
23 + `, "caddyfile")
24 +
25 + // act and assert
26 + caddytest.AssertGetResponse(t, "http://localhost:9080/version", 200, "hello from a.caddy.localhost")
27 +}
28 +
29 +func TestRedirect(t *testing.T) {
30 +
31 + // arrange
32 + caddytest.InitServer(t, `
33 + {
34 + http_port 9080
35 + https_port 9443
36 + }
37 +
38 + localhost:9080 {
39 +
40 + redir / http://localhost:9080/hello 301
41 +
42 + respond /hello 200 {
43 + body "hello from b"
44 + }
45 + }
46 + `, "caddyfile")
47 +
48 + // act and assert
49 + caddytest.AssertRedirect(t, "http://localhost:9080/", "http://localhost:9080/hello", 301)
50 +
51 + // follow redirect
52 + caddytest.AssertGetResponse(t, "http://localhost:9080/", 200, "hello from b")
53 +}
54 +
55 +func TestDuplicateHosts(t *testing.T) {
56 +
57 + // act and assert
58 + caddytest.AssertLoadError(t,
59 + `
60 + localhost:9080 {
61 + }
62 +
63 + localhost:9080 {
64 + }
65 + `,
66 + "caddyfile",
67 + "duplicate site address not allowed")
68 +}
69 +
70 +func TestDefaultSNI(t *testing.T) {
71 +
72 + // arrange
73 + caddytest.InitServer(t, `
74 + {
75 + http_port 9080
76 + https_port 9443
77 + default_sni *.caddy.localhost
78 + }
79 +
80 + 127.0.0.1:9443 {
81 + tls /caddy.localhost.crt /caddy.localhost.key {
82 + }
83 + respond /version 200 {
84 + body "hello from a.caddy.localhost"
85 + }
86 + }
87 + `, "caddyfile")
88 +
89 + // act and assert
90 + caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
91 +}