@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 190 lines 6.09 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddytls
16
17 import (
18 "context"
19 "crypto/tls"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "net/http"
24 "net/url"
25 "time"
26
27 "github.com/caddyserver/certmagic"
28 "go.uber.org/zap"
29 "go.uber.org/zap/zapcore"
30
31 "github.com/caddyserver/caddy/v2"
32 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
33 )
34
35 func init() {
36 caddy.RegisterModule(PermissionByHTTP{})
37 }
38
39 // OnDemandConfig configures on-demand TLS, for obtaining
40 // needed certificates at handshake-time. Because this
41 // feature can easily be abused, Caddy must ask permission
42 // to your application whether a particular domain is allowed
43 // to have a certificate issued for it.
44 type OnDemandConfig struct {
45 // Deprecated. WILL BE REMOVED SOON. Use 'permission' instead with the `http` module.
46 Ask string `json:"ask,omitempty"`
47
48 // REQUIRED. A module that will determine whether a
49 // certificate is allowed to be loaded from storage
50 // or obtained from an issuer on demand.
51 PermissionRaw json.RawMessage `json:"permission,omitempty" caddy:"namespace=tls.permission inline_key=module"`
52 permission OnDemandPermission
53 }
54
55 // OnDemandPermission is a type that can give permission for
56 // whether a certificate should be allowed to be obtained or
57 // loaded from storage on-demand.
58 // EXPERIMENTAL: This API is experimental and subject to change.
59 type OnDemandPermission interface {
60 // CertificateAllowed returns nil if a certificate for the given
61 // name is allowed to be either obtained from an issuer or loaded
62 // from storage on-demand.
63 //
64 // The context passed in has the associated *tls.ClientHelloInfo
65 // value available at the certmagic.ClientHelloInfoCtxKey key.
66 //
67 // In the worst case, this function may be called as frequently
68 // as every TLS handshake, so it should return as quick as possible
69 // to reduce latency. In the normal case, this function is only
70 // called when a certificate is needed that is not already loaded
71 // into memory ready to serve.
72 CertificateAllowed(ctx context.Context, name string) error
73 }
74
75 // PermissionByHTTP determines permission for a TLS certificate by
76 // making a request to an HTTP endpoint.
77 type PermissionByHTTP struct {
78 // The endpoint to access. It should be a full URL.
79 // A query string parameter "domain" will be added to it,
80 // containing the domain (or IP) for the desired certificate,
81 // like so: `?domain=example.com`. Generally, this endpoint
82 // is not exposed publicly to avoid a minor information leak
83 // (which domains are serviced by your application).
84 //
85 // The endpoint must return a 200 OK status if a certificate
86 // is allowed; anything else will cause it to be denied.
87 // Redirects are not followed.
88 Endpoint string `json:"endpoint"`
89
90 logger *zap.Logger
91 replacer *caddy.Replacer
92 }
93
94 // CaddyModule returns the Caddy module information.
95 func (PermissionByHTTP) CaddyModule() caddy.ModuleInfo {
96 return caddy.ModuleInfo{
97 ID: "tls.permission.http",
98 New: func() caddy.Module { return new(PermissionByHTTP) },
99 }
100 }
101
102 // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
103 func (p *PermissionByHTTP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
104 if !d.Next() {
105 return nil
106 }
107 if !d.AllArgs(&p.Endpoint) {
108 return d.ArgErr()
109 }
110 return nil
111 }
112
113 func (p *PermissionByHTTP) Provision(ctx caddy.Context) error {
114 p.logger = ctx.Logger()
115 p.replacer = caddy.NewReplacer()
116 return nil
117 }
118
119 func (p PermissionByHTTP) CertificateAllowed(ctx context.Context, name string) error {
120 // run replacer on endpoint URL (for environment variables) -- return errors to prevent surprises (#5036)
121 askEndpoint, err := p.replacer.ReplaceOrErr(p.Endpoint, true, true)
122 if err != nil {
123 return fmt.Errorf("preparing 'ask' endpoint: %v", err)
124 }
125
126 askURL, err := url.Parse(askEndpoint)
127 if err != nil {
128 return fmt.Errorf("parsing ask URL: %v", err)
129 }
130 qs := askURL.Query()
131 qs.Set("domain", name)
132 askURL.RawQuery = qs.Encode()
133 askURLString := askURL.String()
134
135 var remote string
136 if chi, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && chi != nil {
137 remote = chi.Conn.RemoteAddr().String()
138 }
139
140 if c := p.logger.Check(zapcore.DebugLevel, "asking permission endpoint"); c != nil {
141 c.Write(
142 zap.String("remote", remote),
143 zap.String("domain", name),
144 zap.String("url", askURLString),
145 )
146 }
147
148 resp, err := onDemandAskClient.Get(askURLString)
149 if err != nil {
150 return fmt.Errorf("checking %v to determine if certificate for hostname '%s' should be allowed: %v",
151 askEndpoint, name, err)
152 }
153 resp.Body.Close()
154
155 if c := p.logger.Check(zapcore.DebugLevel, "response from permission endpoint"); c != nil {
156 c.Write(
157 zap.String("remote", remote),
158 zap.String("domain", name),
159 zap.String("url", askURLString),
160 zap.Int("status", resp.StatusCode),
161 )
162 }
163
164 if resp.StatusCode < 200 || resp.StatusCode > 299 {
165 return fmt.Errorf("%s: %w %s - non-2xx status code %d", name, ErrPermissionDenied, askEndpoint, resp.StatusCode)
166 }
167
168 return nil
169 }
170
171 // ErrPermissionDenied is an error that should be wrapped or returned when the
172 // configured permission module does not allow a certificate to be issued,
173 // to distinguish that from other errors such as connection failure.
174 var ErrPermissionDenied = errors.New("certificate not allowed by permission module")
175
176 // These perpetual values are used for on-demand TLS.
177 var (
178 onDemandAskClient = &http.Client{
179 Timeout: 10 * time.Second,
180 CheckRedirect: func(req *http.Request, via []*http.Request) error {
181 return fmt.Errorf("following http redirects is not allowed")
182 },
183 }
184 )
185
186 // Interface guards
187 var (
188 _ OnDemandPermission = (*PermissionByHTTP)(nil)
189 _ caddy.Provisioner = (*PermissionByHTTP)(nil)
190 )