@@ -36,7 +36,7 @@ type P2PForgeCertMgr struct {
36
36
ProvideHost func (host.Host )
37
37
hostFn func () host.Host
38
38
hasHost func () bool
39
- cfg * certmagic.Config
39
+ certmagic * certmagic.Config
40
40
log * zap.SugaredLogger
41
41
allowPrivateForgeAddresses bool
42
42
produceShortAddrs bool
@@ -45,11 +45,6 @@ type P2PForgeCertMgr struct {
45
45
certCheckMx sync.RWMutex
46
46
}
47
47
48
- var (
49
- defaultCertCache * certmagic.Cache
50
- defaultCertCacheMu sync.Mutex
51
- )
52
-
53
48
func isRelayAddr (a multiaddr.Multiaddr ) bool {
54
49
found := false
55
50
multiaddr .ForEach (a , func (c multiaddr.Component ) bool {
@@ -84,9 +79,11 @@ type P2PForgeCertMgrConfig struct {
84
79
storage certmagic.Storage
85
80
modifyForgeRequest func (r * http.Request ) error
86
81
onCertLoaded func ()
82
+ onCertRenewed func ()
87
83
log * zap.SugaredLogger
88
84
allowPrivateForgeAddresses bool
89
85
produceShortAddrs bool
86
+ renewCheckInterval time.Duration
90
87
}
91
88
92
89
type P2PForgeCertMgrOptions func (* P2PForgeCertMgrConfig ) error
@@ -176,6 +173,22 @@ func WithTrustedRoots(trustedRoots *x509.CertPool) P2PForgeCertMgrOptions {
176
173
}
177
174
}
178
175
176
+ // WithOnCertRenewed is optional callback executed on cert renewal event
177
+ func WithOnCertRenewed (fn func ()) P2PForgeCertMgrOptions {
178
+ return func (config * P2PForgeCertMgrConfig ) error {
179
+ config .onCertRenewed = fn
180
+ return nil
181
+ }
182
+ }
183
+
184
+ // WithRenewCheckInterval is meant for testing
185
+ func WithRenewCheckInterval (renewCheckInterval time.Duration ) P2PForgeCertMgrOptions {
186
+ return func (config * P2PForgeCertMgrConfig ) error {
187
+ config .renewCheckInterval = renewCheckInterval
188
+ return nil
189
+ }
190
+ }
191
+
179
192
// WithAllowPrivateForgeAddrs is meant for testing or skipping all the
180
193
// connectivity checks libp2p node needs to pass before it can request domain
181
194
// and start ACME DNS-01 challenge.
@@ -210,41 +223,13 @@ func WithLogger(log *zap.SugaredLogger) P2PForgeCertMgrOptions {
210
223
}
211
224
}
212
225
213
- // newCertmagicConfig is p2p-forge/client-specific version of
214
- // certmagic.NewDefault() that ensures we have our own cert cache. This is
215
- // necessary to ensure cert maintenance spawned by NewCache does not share
216
- // global certmagic.Default.Storage, and certmagic.Default.Logger and uses
217
- // storage path specific to p2p-forge, and no other instance of certmagic in
218
- // golang application.
219
- func newCertmagicConfig (mgrCfg * P2PForgeCertMgrConfig ) * certmagic.Config {
220
- clog := mgrCfg .log .Desugar ()
221
-
222
- defaultCertCacheMu .Lock ()
223
- if defaultCertCache == nil {
224
- defaultCertCache = certmagic .NewCache (certmagic.CacheOptions {
225
- GetConfigForCert : func (certmagic.Certificate ) (* certmagic.Config , error ) {
226
- // default getter that does not depend on certmagic defaults
227
- // and respects Config.Storage path
228
- return newCertmagicConfig (mgrCfg ), nil
229
- },
230
- Logger : clog ,
231
- })
232
- }
233
- certCache := defaultCertCache
234
- defaultCertCacheMu .Unlock ()
235
-
236
- return certmagic .New (certCache , certmagic.Config {
237
- Storage : mgrCfg .storage ,
238
- Logger : clog ,
239
- })
240
- }
241
-
242
226
// NewP2PForgeCertMgr handles the creation and management of certificates that are automatically granted by a forge
243
227
// to a libp2p host.
244
228
//
245
229
// Calling this function signifies your acceptance to
246
230
// the CA's Subscriber Agreement and/or Terms of Service. Let's Encrypt is the default CA.
247
231
func NewP2PForgeCertMgr (opts ... P2PForgeCertMgrOptions ) (* P2PForgeCertMgr , error ) {
232
+ // Init config + apply optional user settings
248
233
mgrCfg := & P2PForgeCertMgrConfig {}
249
234
for _ , opt := range opts {
250
235
if err := opt (mgrCfg ); err != nil {
@@ -270,14 +255,11 @@ func NewP2PForgeCertMgr(opts ...P2PForgeCertMgrOptions) (*P2PForgeCertMgr, error
270
255
return nil , fmt .Errorf ("must specify the forge registration endpoint if using a non-default forge" )
271
256
}
272
257
}
273
-
274
- const defaultStorageLocation = "p2p-forge-certs"
275
258
if mgrCfg .storage == nil {
276
- mgrCfg .storage = & certmagic.FileStorage {Path : defaultStorageLocation }
259
+ mgrCfg .storage = & certmagic.FileStorage {Path : DefaultStorageLocation }
277
260
}
278
261
279
- certCfg := newCertmagicConfig (mgrCfg )
280
-
262
+ // Wire up p2p-forge manager instance
281
263
hostChan := make (chan host.Host , 1 )
282
264
provideHost := func (host host.Host ) { hostChan <- host }
283
265
hasHostChan := make (chan struct {})
@@ -293,39 +275,60 @@ func NewP2PForgeCertMgr(opts ...P2PForgeCertMgrOptions) (*P2PForgeCertMgr, error
293
275
defer close (hasHostChan )
294
276
return <- hostChan
295
277
})
278
+ mgr := & P2PForgeCertMgr {
279
+ forgeDomain : mgrCfg .forgeDomain ,
280
+ forgeRegistrationEndpoint : mgrCfg .forgeRegistrationEndpoint ,
281
+ ProvideHost : provideHost ,
282
+ hostFn : hostFn ,
283
+ hasHost : hasHostFn ,
284
+ log : mgrCfg .log ,
285
+ allowPrivateForgeAddresses : mgrCfg .allowPrivateForgeAddresses ,
286
+ produceShortAddrs : mgrCfg .produceShortAddrs ,
287
+ }
288
+
289
+ // NOTE: callback getter is necessary to avoid circular dependency
290
+ // but also structure code to avoid issues like https://github.com/ipshipyard/p2p-forge/issues/28
291
+ configGetter := func (cert certmagic.Certificate ) (* certmagic.Config , error ) {
292
+ if mgr .certmagic == nil {
293
+ return nil , errors .New ("P2PForgeCertmgr.certmagic is not set" )
294
+ }
295
+ return mgr .certmagic , nil
296
+ }
297
+
298
+ magicCache := certmagic .NewCache (certmagic.CacheOptions {
299
+ GetConfigForCert : configGetter ,
300
+ RenewCheckInterval : mgrCfg .renewCheckInterval ,
301
+ Logger : mgrCfg .log .Desugar (),
302
+ })
296
303
297
- myACME := certmagic .NewACMEIssuer (certCfg , certmagic.ACMEIssuer { // TODO: UX around user passed emails + agreement
304
+ // Wire up final certmagic config by calling upstream New with sanity checks
305
+ mgr .certmagic = certmagic .New (magicCache , certmagic.Config {
306
+ Storage : mgrCfg .storage ,
307
+ Logger : mgrCfg .log .Desugar (),
308
+ })
309
+
310
+ // Wire up Issuer that does brokered DNS-01 ACME challenge
311
+ acmeLog := mgrCfg .log .Named ("acme-broker" )
312
+ brokeredDNS01Issuer := certmagic .NewACMEIssuer (mgr .certmagic , certmagic.ACMEIssuer {
298
313
CA : mgrCfg .caEndpoint ,
299
314
Email : mgrCfg .userEmail ,
300
315
Agreed : true ,
301
316
DNS01Solver : & dns01P2PForgeSolver {
302
317
forgeRegistrationEndpoint : mgrCfg .forgeRegistrationEndpoint ,
303
318
forgeAuth : mgrCfg .forgeAuth ,
304
- hostFn : hostFn ,
319
+ hostFn : mgr . hostFn ,
305
320
modifyForgeRequest : mgrCfg .modifyForgeRequest ,
306
321
userAgent : mgrCfg .userAgent ,
307
322
allowPrivateForgeAddresses : mgrCfg .allowPrivateForgeAddresses ,
308
- log : mgrCfg . log .Named ("dns01solver" ),
323
+ log : acmeLog .Named ("dns01solver" ),
309
324
},
310
325
TrustedRoots : mgrCfg .trustedRoots ,
311
- Logger : certCfg . Logger ,
326
+ Logger : acmeLog . Desugar () ,
312
327
})
328
+ mgr .certmagic .Issuers = []certmagic.Issuer {brokeredDNS01Issuer }
313
329
314
- certCfg .Issuers = []certmagic.Issuer {myACME }
315
-
316
- mgr := & P2PForgeCertMgr {
317
- forgeDomain : mgrCfg .forgeDomain ,
318
- forgeRegistrationEndpoint : mgrCfg .forgeRegistrationEndpoint ,
319
- ProvideHost : provideHost ,
320
- hostFn : hostFn ,
321
- hasHost : hasHostFn ,
322
- cfg : certCfg ,
323
- log : mgrCfg .log ,
324
- allowPrivateForgeAddresses : mgrCfg .allowPrivateForgeAddresses ,
325
- produceShortAddrs : mgrCfg .produceShortAddrs ,
326
- }
327
-
328
- certCfg .OnEvent = func (ctx context.Context , event string , data map [string ]any ) error {
330
+ // Wire up onCertLoaded callback
331
+ mgr .certmagic .OnEvent = func (ctx context.Context , event string , data map [string ]any ) error {
329
332
if event == "cached_managed_cert" {
330
333
sans , ok := data ["sans" ]
331
334
if ! ok {
@@ -352,24 +355,39 @@ func NewP2PForgeCertMgr(opts ...P2PForgeCertMgrOptions) (*P2PForgeCertMgr, error
352
355
}
353
356
return nil
354
357
}
358
+
359
+ // Execute user function for on certificate cert renewal
360
+ if event == "cert_obtained" && mgrCfg .onCertRenewed != nil {
361
+ if renewal , ok := data ["renewal" ].(bool ); ok && renewal {
362
+ name := certName (hostFn ().ID (), mgrCfg .forgeDomain )
363
+ if id , ok := data ["identifier" ].(string ); ok && id == name {
364
+ mgrCfg .onCertRenewed ()
365
+ }
366
+ }
367
+ return nil
368
+ }
369
+
355
370
return nil
356
371
}
357
372
358
373
return mgr , nil
359
374
}
360
375
361
376
func (m * P2PForgeCertMgr ) Start () error {
362
- if m .cfg == nil || m .hostFn == nil {
377
+ if m .certmagic == nil || m .hostFn == nil {
363
378
return errors .New ("unable to start without a certmagic and libp2p host" )
364
379
}
380
+ if m .certmagic .Storage == nil {
381
+ return errors .New ("unable to start without a certmagic Cache and Storage set up" )
382
+ }
365
383
m .ctx , m .cancel = context .WithCancel (context .Background ())
366
384
go func () {
367
385
log := m .log .Named ("start" )
368
386
h := m .hostFn ()
369
387
name := certName (h .ID (), m .forgeDomain )
370
- certExists := localCertExists (m .ctx , m .cfg , name )
388
+ certExists := localCertExists (m .ctx , m .certmagic , name )
371
389
startCertManagement := func () {
372
- if err := m .cfg .ManageAsync (m .ctx , []string {name }); err != nil {
390
+ if err := m .certmagic .ManageAsync (m .ctx , []string {name }); err != nil {
373
391
log .Error (err )
374
392
}
375
393
}
@@ -433,8 +451,9 @@ func (m *P2PForgeCertMgr) Stop() {
433
451
434
452
// TLSConfig returns a tls.Config that managed by the P2PForgeCertMgr
435
453
func (m * P2PForgeCertMgr ) TLSConfig () * tls.Config {
436
- tlsCfg := m .cfg .TLSConfig ()
454
+ tlsCfg := m .certmagic .TLSConfig ()
437
455
tlsCfg .NextProtos = nil // remove the ACME ALPN
456
+ tlsCfg .GetCertificate = m .certmagic .GetCertificate
438
457
return tlsCfg
439
458
}
440
459
@@ -449,7 +468,7 @@ func (m *P2PForgeCertMgr) AddrStrings() []string {
449
468
// This should be used with the libp2p.AddrsFactory option to ensure that a libp2p host with forge managed addresses
450
469
// only announces those that are active and valid.
451
470
func (m * P2PForgeCertMgr ) AddressFactory () config.AddrsFactory {
452
- tlsCfg := m .cfg .TLSConfig ()
471
+ tlsCfg := m .certmagic .TLSConfig ()
453
472
tlsCfg .NextProtos = []string {"h2" , "http/1.1" } // remove the ACME ALPN and set the HTTP 1.1 and 2 ALPNs
454
473
455
474
return m .createAddrsFactory (m .allowPrivateForgeAddresses , m .produceShortAddrs )
0 commit comments