@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 209 lines 5.67 KB
Raw
1 package integration
2
3 import (
4 "context"
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 "fmt"
9 "log/slog"
10 "net"
11 "net/http"
12 "strings"
13 "testing"
14
15 "github.com/mholt/acmez/v3"
16 "github.com/mholt/acmez/v3/acme"
17 smallstepacme "github.com/smallstep/certificates/acme"
18 "go.uber.org/zap"
19 "go.uber.org/zap/exp/zapslog"
20
21 "github.com/caddyserver/caddy/v2"
22 "github.com/caddyserver/caddy/v2/caddytest"
23 )
24
25 const acmeChallengePort = 9081
26
27 // Test the basic functionality of Caddy's ACME server
28 func TestACMEServerWithDefaults(t *testing.T) {
29 ctx := context.Background()
30 logger, err := zap.NewDevelopment()
31 if err != nil {
32 t.Error(err)
33 return
34 }
35
36 tester := caddytest.NewTester(t)
37 tester.InitServer(`
38 {
39 skip_install_trust
40 admin localhost:2999
41 http_port 9080
42 https_port 9443
43 local_certs
44 }
45 acme.localhost {
46 acme_server
47 }
48 `, "caddyfile")
49
50 client := acmez.Client{
51 Client: &acme.Client{
52 Directory: "https://acme.localhost:9443/acme/local/directory",
53 HTTPClient: tester.Client,
54 Logger: slog.New(zapslog.NewHandler(logger.Core(), zapslog.WithName("acmez"))),
55 },
56 ChallengeSolvers: map[string]acmez.Solver{
57 acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
58 },
59 }
60
61 accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
62 if err != nil {
63 t.Errorf("generating account key: %v", err)
64 }
65 account := acme.Account{
66 Contact: []string{"mailto:you@example.com"},
67 TermsOfServiceAgreed: true,
68 PrivateKey: accountPrivateKey,
69 }
70 account, err = client.NewAccount(ctx, account)
71 if err != nil {
72 t.Errorf("new account: %v", err)
73 return
74 }
75
76 // Every certificate needs a key.
77 certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
78 if err != nil {
79 t.Errorf("generating certificate key: %v", err)
80 return
81 }
82
83 certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"})
84 if err != nil {
85 t.Errorf("obtaining certificate: %v", err)
86 return
87 }
88
89 // ACME servers should usually give you the entire certificate chain
90 // in PEM format, and sometimes even alternate chains! It's up to you
91 // which one(s) to store and use, but whatever you do, be sure to
92 // store the certificate and key somewhere safe and secure, i.e. don't
93 // lose them!
94 for _, cert := range certs {
95 t.Logf("Certificate %q:\n%s\n\n", cert.URL, cert.ChainPEM)
96 }
97 }
98
99 func TestACMEServerWithMismatchedChallenges(t *testing.T) {
100 ctx := context.Background()
101 logger := caddy.Log().Named("acmez")
102
103 tester := caddytest.NewTester(t)
104 tester.InitServer(`
105 {
106 skip_install_trust
107 admin localhost:2999
108 http_port 9080
109 https_port 9443
110 local_certs
111 }
112 acme.localhost {
113 acme_server {
114 challenges tls-alpn-01
115 }
116 }
117 `, "caddyfile")
118
119 client := acmez.Client{
120 Client: &acme.Client{
121 Directory: "https://acme.localhost:9443/acme/local/directory",
122 HTTPClient: tester.Client,
123 Logger: slog.New(zapslog.NewHandler(logger.Core(), zapslog.WithName("acmez"))),
124 },
125 ChallengeSolvers: map[string]acmez.Solver{
126 acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
127 },
128 }
129
130 accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
131 if err != nil {
132 t.Errorf("generating account key: %v", err)
133 }
134 account := acme.Account{
135 Contact: []string{"mailto:you@example.com"},
136 TermsOfServiceAgreed: true,
137 PrivateKey: accountPrivateKey,
138 }
139 account, err = client.NewAccount(ctx, account)
140 if err != nil {
141 t.Errorf("new account: %v", err)
142 return
143 }
144
145 // Every certificate needs a key.
146 certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
147 if err != nil {
148 t.Errorf("generating certificate key: %v", err)
149 return
150 }
151
152 certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"})
153 if len(certs) > 0 {
154 t.Errorf("expected '0' certificates, but received '%d'", len(certs))
155 }
156 if err == nil {
157 t.Error("expected errors, but received none")
158 }
159 const expectedErrMsg = "no solvers available for remaining challenges (configured=[http-01] offered=[tls-alpn-01] remaining=[tls-alpn-01])"
160 if !strings.Contains(err.Error(), expectedErrMsg) {
161 t.Errorf(`received error message does not match expectation: expected="%s" received="%s"`, expectedErrMsg, err.Error())
162 }
163 }
164
165 // naiveHTTPSolver is a no-op acmez.Solver for example purposes only.
166 type naiveHTTPSolver struct {
167 srv *http.Server
168 logger *zap.Logger
169 }
170
171 func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme.Challenge) error {
172 smallstepacme.InsecurePortHTTP01 = acmeChallengePort
173 s.srv = &http.Server{
174 Addr: fmt.Sprintf(":%d", acmeChallengePort),
175 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
176 host, _, err := net.SplitHostPort(r.Host)
177 if err != nil {
178 host = r.Host
179 }
180 s.logger.Info("received request on challenge server", zap.String("path", r.URL.Path))
181 if r.Method == "GET" && r.URL.Path == challenge.HTTP01ResourcePath() && strings.EqualFold(host, challenge.Identifier.Value) {
182 w.Header().Add("Content-Type", "text/plain")
183 w.Write([]byte(challenge.KeyAuthorization))
184 r.Close = true
185 s.logger.Info("served key authentication",
186 zap.String("identifier", challenge.Identifier.Value),
187 zap.String("challenge", "http-01"),
188 zap.String("remote", r.RemoteAddr),
189 )
190 }
191 }),
192 }
193 l, err := net.Listen("tcp", fmt.Sprintf(":%d", acmeChallengePort))
194 if err != nil {
195 return err
196 }
197 s.logger.Info("present challenge", zap.Any("challenge", challenge))
198 go s.srv.Serve(l)
199 return nil
200 }
201
202 func (s naiveHTTPSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
203 smallstepacme.InsecurePortHTTP01 = 0
204 s.logger.Info("cleanup", zap.Any("challenge", challenge))
205 if s.srv != nil {
206 s.srv.Close()
207 }
208 return nil
209 }