@@ -2,22 +2,13 @@ package acme
2
2
3
3
import (
4
4
"context"
5
- "crypto/ecdsa"
6
- "crypto/elliptic"
7
- "crypto/rand"
8
5
"crypto/tls"
9
6
"crypto/x509"
10
7
"fmt"
11
8
"os"
12
9
"slices"
13
10
"time"
14
11
15
- "github.com/go-acme/lego/v4/acme"
16
- "github.com/go-acme/lego/v4/certcrypto"
17
- "github.com/go-acme/lego/v4/certificate"
18
- "github.com/go-acme/lego/v4/lego"
19
- "github.com/go-acme/lego/v4/registration"
20
-
21
12
"github.com/lxc/incus/v6/internal/server/state"
22
13
internalUtil "github.com/lxc/incus/v6/internal/util"
23
14
"github.com/lxc/incus/v6/shared/logger"
@@ -36,14 +27,20 @@ const retries = 5
36
27
// certificate at a later stage.
37
28
const ClusterCertFilename = "cluster.crt.new"
38
29
30
+ // CertKeyPair describes a certificate and its private key.
31
+ type CertKeyPair struct {
32
+ Certificate []byte `json:"-"`
33
+ PrivateKey []byte `json:"-"`
34
+ }
35
+
39
36
// certificateNeedsUpdate returns true if the domain doesn't match the certificate's DNS names
40
37
// or it's valid for less than 30 days.
41
38
func certificateNeedsUpdate (domain string , cert * x509.Certificate ) bool {
42
39
return ! slices .Contains (cert .DNSNames , domain ) || time .Now ().After (cert .NotAfter .Add (- 30 * 24 * time .Hour ))
43
40
}
44
41
45
42
// UpdateCertificate updates the certificate.
46
- func UpdateCertificate (s * state.State , challengeType string , provider ChallengeProvider , clustered bool , domain string , email string , caURL string , force bool ) (* certificate. Resource , error ) {
43
+ func UpdateCertificate (s * state.State , challengeType string , clustered bool , domain string , email string , caURL string , force bool ) (* CertKeyPair , error ) {
47
44
clusterCertFilename := internalUtil .VarPath (ClusterCertFilename )
48
45
49
46
l := logger .AddContext (logger.Ctx {"domain" : domain , "caURL" : caURL , "challenge" : challengeType })
@@ -75,7 +72,7 @@ func UpdateCertificate(s *state.State, challengeType string, provider ChallengeP
75
72
}
76
73
77
74
if ! certificateNeedsUpdate (domain , cert ) {
78
- return & certificate. Resource {
75
+ return & CertKeyPair {
79
76
Certificate : clusterCert ,
80
77
PrivateKey : key ,
81
78
}, nil
@@ -102,148 +99,66 @@ func UpdateCertificate(s *state.State, challengeType string, provider ChallengeP
102
99
return nil , nil
103
100
}
104
101
102
+ tmpDir , err := os .MkdirTemp ("" , "lego" )
103
+ if err != nil {
104
+ return nil , fmt .Errorf ("Failed to create temporary directory: %w" , err )
105
+ }
106
+
107
+ defer func () {
108
+ err := os .RemoveAll (tmpDir )
109
+ if err != nil {
110
+ logger .Warn ("Failed to remove temporary directory" , logger.Ctx {"err" : err })
111
+ }
112
+ }()
113
+
114
+ env := os .Environ ()
115
+
116
+ args := []string {
117
+ "--accept-tos" ,
118
+ "--domains" , domain ,
119
+ "--email" , email ,
120
+ "--path" , tmpDir ,
121
+ "--server" , caURL ,
122
+ }
123
+
105
124
if challengeType == "DNS-01" {
106
125
provider , environment , resolvers := s .GlobalConfig .ACMEDNS ()
107
126
127
+ env = append (env , environment ... )
128
+
108
129
if provider == "" {
109
130
return nil , fmt .Errorf ("DNS-01 challenge type requires acme.dns.provider configuration key to be set" )
110
131
}
111
132
112
- tmpDir , err := os .MkdirTemp ("" , "lego" )
113
- if err != nil {
114
- return nil , fmt .Errorf ("Failed to create temporary directory: %w" , err )
115
- }
116
-
117
- defer func () {
118
- err := os .RemoveAll (tmpDir )
119
- if err != nil {
120
- logger .Warn ("Failed to remove temporary directory" , logger.Ctx {"err" : err })
121
- }
122
- }()
123
-
124
- args := []string {
125
- "--accept-tos" ,
126
- "--dns" , provider ,
127
- "--domains" , domain ,
128
- "--email" , email ,
129
- "--path" , tmpDir ,
130
- "--server" , caURL ,
131
- }
132
-
133
+ args = append (args , "--dns" , provider )
133
134
if len (resolvers ) > 0 {
134
135
for _ , resolver := range resolvers {
135
136
args = append (args , "--dns.resolvers" , resolver )
136
137
}
137
138
}
139
+ } else if challengeType == "HTTP-01" {
140
+ args = append (args , "--http" )
138
141
139
- args = append (args , "run" )
140
-
141
- _ , _ , err = subprocess .RunCommandSplit (context .TODO (), append (os .Environ (), environment ... ), nil , "lego" , args ... )
142
- if err != nil {
143
- return nil , fmt .Errorf ("Failed to run lego command: %w" , err )
142
+ port := s .GlobalConfig .ACMEHTTP ()
143
+ if port != "" {
144
+ args = append (args , "--http.port" , port )
144
145
}
145
-
146
- certInfo , err = localtls .KeyPairAndCA (tmpDir + "/certificates" , domain , localtls .CertServer , true )
147
- if err != nil {
148
- return nil , fmt .Errorf ("Failed to load certificate and key file: %w" , err )
149
- }
150
-
151
- return & certificate.Resource {
152
- Certificate : certInfo .PublicKey (),
153
- PrivateKey : certInfo .PrivateKey (),
154
- }, nil
155
- }
156
-
157
- // Generate new private key for user. This key needs to be different from the server's private key.
158
- privateKey , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
159
- if err != nil {
160
- return nil , fmt .Errorf ("Failed generating private key for user account: %w" , err )
161
- }
162
-
163
- user := user {
164
- Email : email ,
165
- Key : privateKey ,
166
146
}
167
147
168
- config := lego . NewConfig ( & user )
148
+ args = append ( args , "run" )
169
149
170
- if caURL != "" {
171
- config .CADirURL = caURL
172
- } else {
173
- // Default URL for Let's Encrypt
174
- config .CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
175
- }
176
-
177
- config .Certificate .KeyType = certcrypto .RSA2048
178
-
179
- client , err := lego .NewClient (config )
150
+ _ , _ , err = subprocess .RunCommandSplit (context .TODO (), env , nil , "lego" , args ... )
180
151
if err != nil {
181
- return nil , fmt .Errorf ("Failed to create new client : %w" , err )
152
+ return nil , fmt .Errorf ("Failed to run lego command : %w" , err )
182
153
}
183
154
184
- err = provider . RegisterWithSolver ( client . Challenge )
155
+ certInfo , err = localtls . KeyPairAndCA ( tmpDir + "/certificates" , domain , localtls . CertServer , true )
185
156
if err != nil {
186
- return nil , fmt .Errorf ("Failed setting challenge provider: %w" , err )
187
- }
188
-
189
- var reg * registration.Resource
190
-
191
- // Registration might fail randomly (as seen in manual tests), so retry in that case.
192
- for i := 0 ; i < retries ; i ++ {
193
- reg , err = client .Registration .Register (registration.RegisterOptions {TermsOfServiceAgreed : true })
194
- if err == nil {
195
- break
196
- }
197
-
198
- // In case we were rate limited, don't try again.
199
- details , ok := err .(* acme.ProblemDetails )
200
- if ok && details .Type == "urn:ietf:params:acme:error:rateLimited" {
201
- break
202
- }
203
-
204
- l .Warn ("Failed to register user, retrying in 10 seconds" , logger.Ctx {"err" : err })
205
- time .Sleep (10 * time .Second )
206
- }
207
-
208
- if err != nil {
209
- return nil , fmt .Errorf ("Failed to register user: %w" , err )
210
- }
211
-
212
- user .Registration = reg
213
-
214
- request := certificate.ObtainRequest {
215
- Domains : []string {domain },
216
- Bundle : true ,
217
- PrivateKey : certInfo .KeyPair ().PrivateKey ,
218
- }
219
-
220
- var certificates * certificate.Resource
221
-
222
- l .Info ("Issuing certificate" )
223
-
224
- // Get new certificate.
225
- // This might fail randomly (as seen in manual tests), so retry in that case.
226
- for i := 0 ; i < retries ; i ++ {
227
- certificates , err = client .Certificate .Obtain (request )
228
- if err == nil {
229
- break
230
- }
231
-
232
- // In case we were rate limited, don't try again.
233
- details , ok := err .(* acme.ProblemDetails )
234
- if ok && details .Type == "urn:ietf:params:acme:error:rateLimited" {
235
- break
236
- }
237
-
238
- l .Warn ("Failed to obtain certificate, retrying in 10 seconds" , logger.Ctx {"err" : err })
239
- time .Sleep (10 * time .Second )
240
- }
241
-
242
- if err != nil {
243
- return nil , fmt .Errorf ("Failed to obtain certificate: %w" , err )
157
+ return nil , fmt .Errorf ("Failed to load certificate and key file: %w" , err )
244
158
}
245
159
246
- l .Info ("Finished issuing certificate" )
247
-
248
- return certificates , nil
160
+ return & CertKeyPair {
161
+ Certificate : certInfo .PublicKey (),
162
+ PrivateKey : certInfo .PrivateKey (),
163
+ }, nil
249
164
}
0 commit comments