forked from caddyserver/certmagic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
solvers.go
148 lines (132 loc) · 5.16 KB
/
solvers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2015 Matthew Holt
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package certmagic
import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/tlsalpn01"
)
// tlsALPNSolver is a type that can solve TLS-ALPN challenges using
// an existing listener and our custom, in-memory certificate cache.
type tlsALPNSolver struct {
certCache *Cache
}
// Present adds the challenge certificate to the cache.
func (s tlsALPNSolver) Present(domain, token, keyAuth string) error {
cert, err := tlsalpn01.ChallengeCert(domain, keyAuth)
if err != nil {
return err
}
certHash := hashCertificateChain(cert.Certificate)
s.certCache.mu.Lock()
s.certCache.cache[tlsALPNCertKeyName(domain)] = Certificate{
Certificate: *cert,
Names: []string{domain},
Hash: certHash, // perhaps not necesssary
}
s.certCache.mu.Unlock()
return nil
}
// CleanUp removes the challenge certificate from the cache.
func (s tlsALPNSolver) CleanUp(domain, token, keyAuth string) error {
s.certCache.mu.Lock()
delete(s.certCache.cache, tlsALPNCertKeyName(domain))
s.certCache.mu.Unlock()
return nil
}
// tlsALPNCertKeyName returns the key to use when caching a cert
// for use with the TLS-ALPN ACME challenge. It is simply to help
// avoid conflicts (although at time of writing, there shouldn't
// be, since the cert cache is keyed by hash of certificate chain).
func tlsALPNCertKeyName(sniName string) string {
return sniName + ":acme-tls-alpn"
}
// distributedSolver allows the ACME HTTP-01 and TLS-ALPN challenges
// to be solved by an instance other than the one which initiated it.
// This is useful behind load balancers or in other cluster/fleet
// configurations. The only requirement is that the instance which
// initiates the challenge shares the same storage and locker with
// the others in the cluster. The storage backing the certificate
// cache in distributedSolver.config is crucial.
//
// Obviously, the instance which completes the challenge must be
// serving on the HTTPChallengePort for the HTTP-01 challenge or the
// TLSALPNChallengePort for the TLS-ALPN-01 challenge (or have all
// the packets port-forwarded) to receive and handle the request. The
// server which receives the challenge must handle it by checking to
// see if the challenge token exists in storage, and if so, decode it
// and use it to serve up the correct response. HTTPChallengeHandler
// in this package as well as the GetCertificate method implemented
// by a Config support and even require this behavior.
//
// In short: the only two requirements for cluster operation are
// sharing sync and storage, and using the facilities provided by
// this package for solving the challenges.
type distributedSolver struct {
// The config with a certificate cache
// with a reference to the storage to
// use which is shared among all the
// instances in the cluster - REQUIRED.
config *Config
// Since the distributedSolver is only a
// wrapper over an actual solver, place
// the actual solver here.
providerServer challenge.Provider
}
// Present invokes the underlying solver's Present method
// and also stores domain, token, and keyAuth to the storage
// backing the certificate cache of dhs.config.
func (dhs distributedSolver) Present(domain, token, keyAuth string) error {
if dhs.providerServer != nil {
err := dhs.providerServer.Present(domain, token, keyAuth)
if err != nil {
return fmt.Errorf("presenting with standard provider server: %v", err)
}
}
infoBytes, err := json.Marshal(challengeInfo{
Domain: domain,
Token: token,
KeyAuth: keyAuth,
})
if err != nil {
return err
}
return dhs.config.certCache.storage.Store(dhs.challengeTokensKey(domain), infoBytes)
}
// CleanUp invokes the underlying solver's CleanUp method
// and also cleans up any assets saved to storage.
func (dhs distributedSolver) CleanUp(domain, token, keyAuth string) error {
if dhs.providerServer != nil {
err := dhs.providerServer.CleanUp(domain, token, keyAuth)
if err != nil {
log.Printf("[ERROR] Cleaning up standard provider server: %v", err)
}
}
return dhs.config.certCache.storage.Delete(dhs.challengeTokensKey(domain))
}
// challengeTokensPrefix returns the key prefix for challenge info.
func (dhs distributedSolver) challengeTokensPrefix() string {
return filepath.Join(StorageKeys.CAPrefix(dhs.config.CA), "challenge_tokens")
}
// challengeTokensKey returns the key to use to store and access
// challenge info for domain.
func (dhs distributedSolver) challengeTokensKey(domain string) string {
return filepath.Join(dhs.challengeTokensPrefix(), StorageKeys.Safe(domain)+".json")
}
type challengeInfo struct {
Domain, Token, KeyAuth string
}