@libp2p/auto-tls
Advanced tools
Comparing version 0.0.0-0862522fe to 0.0.0-2625cc323
@@ -23,2 +23,3 @@ import { serviceCapabilities, serviceDependencies } from '@libp2p/interface'; | ||
private readonly provisionTimeout; | ||
private readonly provisionRequestTimeout; | ||
private readonly renewThreshold; | ||
@@ -39,2 +40,3 @@ private started; | ||
private readonly domainMapper; | ||
private readonly autoConfirmAddress; | ||
constructor(components: AutoTLSComponents, init?: AutoTLSInit); | ||
@@ -41,0 +43,0 @@ readonly [serviceCapabilities]: string[]; |
import { ClientAuth } from '@libp2p/http-fetch/auth'; | ||
import { serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface'; | ||
import { serviceCapabilities, serviceDependencies, setMaxListeners, start, stop } from '@libp2p/interface'; | ||
import { debounce } from '@libp2p/utils/debounce'; | ||
import { X509Certificate } from '@peculiar/x509'; | ||
import * as acme from 'acme-client'; | ||
import { anySignal } from 'any-signal'; | ||
import delay from 'delay'; | ||
import { Key } from 'interface-datastore'; | ||
@@ -11,5 +13,6 @@ import { base36 } from 'multiformats/bases/base36'; | ||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'; | ||
import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js'; | ||
import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_AUTO_CONFIRM_ADDRESS, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_REQUEST_TIMEOUT, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js'; | ||
import { DomainMapper } from './domain-mapper.js'; | ||
import { createCsr, importFromPem, loadOrCreateKey, supportedAddressesFilter } from './utils.js'; | ||
const RETRY_DELAY = 5_000; | ||
export class AutoTLS { | ||
@@ -28,2 +31,3 @@ log; | ||
provisionTimeout; | ||
provisionRequestTimeout; | ||
renewThreshold; | ||
@@ -44,4 +48,5 @@ started; | ||
domainMapper; | ||
autoConfirmAddress; | ||
constructor(components, init = {}) { | ||
this.log = components.logger.forComponent('libp2p:certificate-manager'); | ||
this.log = components.logger.forComponent('libp2p:auto-tls'); | ||
this.addressManager = components.addressManager; | ||
@@ -57,2 +62,3 @@ this.privateKey = components.privateKey; | ||
this.provisionTimeout = init.provisionTimeout ?? DEFAULT_PROVISION_TIMEOUT; | ||
this.provisionRequestTimeout = init.provisionRequestTimeout ?? DEFAULT_PROVISION_REQUEST_TIMEOUT; | ||
this.renewThreshold = init.renewThreshold ?? DEFAULT_RENEWAL_THRESHOLD; | ||
@@ -64,2 +70,3 @@ this.accountPrivateKeyName = init.accountPrivateKeyName ?? DEFAULT_ACCOUNT_PRIVATE_KEY_NAME; | ||
this.certificateDatastoreKey = init.certificateDatastoreKey ?? DEFAULT_CERTIFICATE_DATASTORE_KEY; | ||
this.autoConfirmAddress = init.autoConfirmAddress ?? DEFAULT_AUTO_CONFIRM_ADDRESS; | ||
this.clientAuth = new ClientAuth(this.privateKey); | ||
@@ -81,6 +88,10 @@ this.started = false; | ||
get [serviceDependencies]() { | ||
return [ | ||
const dependencies = [ | ||
'@libp2p/identify', | ||
'@libp2p/keychain' | ||
]; | ||
if (!this.autoConfirmAddress) { | ||
dependencies.push('@libp2p/autonat'); | ||
} | ||
return dependencies; | ||
} | ||
@@ -94,2 +105,3 @@ async start() { | ||
this.shutdownController = new AbortController(); | ||
setMaxListeners(Infinity, this.shutdownController.signal); | ||
this.started = true; | ||
@@ -105,3 +117,4 @@ } | ||
_onSelfPeerUpdate() { | ||
const addresses = this.addressManager.getAddresses().filter(supportedAddressesFilter); | ||
const addresses = this.addressManager.getAddresses() | ||
.filter(supportedAddressesFilter); | ||
if (addresses.length === 0) { | ||
@@ -120,7 +133,22 @@ this.log('not fetching certificate as we have no public addresses'); | ||
this.fetching = true; | ||
this.fetchCertificate(addresses, { | ||
signal: AbortSignal.timeout(this.provisionTimeout) | ||
Promise.resolve().then(async () => { | ||
let attempt = 0; | ||
while (true) { | ||
if (this.shutdownController?.signal.aborted === true) { | ||
throw this.shutdownController.signal.reason; | ||
} | ||
try { | ||
await this.fetchCertificate(addresses, { | ||
signal: AbortSignal.timeout(this.provisionTimeout) | ||
}); | ||
return; | ||
} | ||
catch (err) { | ||
this.log.error('provisioning certificate failed on attempt %d - %e', attempt++, err); | ||
} | ||
await delay(RETRY_DELAY); | ||
} | ||
}) | ||
.catch(err => { | ||
this.log.error('error fetching certificates - %e', err); | ||
this.log.error('giving up provisioning certificate - %e', err); | ||
}) | ||
@@ -133,3 +161,2 @@ .finally(() => { | ||
this.log('fetching certificate'); | ||
// TODO: handle rate limit errors like "too many new registrations (10) from this IP address in the last 3h0m0s, retry after 2024-11-01 09:22:38 UTC: see https://letsencrypt.org/docs/rate-limits/#new-registrations-per-ip-address" | ||
const certificatePrivateKey = await loadOrCreateKey(this.keychain, this.certificatePrivateKeyName, this.certificatePrivateKeyBits); | ||
@@ -163,3 +190,5 @@ const { pem, cert } = await this.loadOrCreateCertificate(certificatePrivateKey, multiaddrs, options); | ||
this.events.safeDispatchEvent(event, { | ||
detail: this.certificate | ||
detail: { | ||
...this.certificate | ||
} | ||
}); | ||
@@ -227,3 +256,24 @@ } | ||
challengeCreateFn: async (authz, challenge, keyAuthorization) => { | ||
await this.configureAcmeChallengeResponse(multiaddrs, keyAuthorization, options); | ||
const signal = anySignal([this.shutdownController?.signal, options?.signal]); | ||
setMaxListeners(Infinity, signal); | ||
let attempt = 0; | ||
while (true) { | ||
if (signal.aborted) { | ||
throw signal.reason; | ||
} | ||
try { | ||
const timeout = AbortSignal.timeout(this.provisionRequestTimeout); | ||
const signal = anySignal([timeout, options?.signal]); | ||
setMaxListeners(Infinity, timeout, signal); | ||
await this.configureAcmeChallengeResponse(multiaddrs, keyAuthorization, { | ||
...options, | ||
signal | ||
}); | ||
return; | ||
} | ||
catch (err) { | ||
this.log.error('contacting %s failed on attempt %d - %e', this.forgeEndpoint, attempt++, err.cause ?? err); | ||
} | ||
await delay(RETRY_DELAY); | ||
} | ||
}, | ||
@@ -240,3 +290,3 @@ challengeRemoveFn: async (authz, challenge, keyAuthorization) => { | ||
const endpoint = `${this.forgeEndpoint}v1/_acme-challenge`; | ||
this.log('asking %sv1/_acme-challenge to respond to the acme DNS challenge on our behalf', endpoint); | ||
this.log('asking %s to respond to the acme DNS challenge on our behalf', endpoint); | ||
this.log('dialback public addresses: %s', addresses.join(', ')); | ||
@@ -243,0 +293,0 @@ const response = await this.clientAuth.authenticatedFetch(endpoint, { |
export declare const DEFAULT_FORGE_ENDPOINT = "https://registration.libp2p.direct"; | ||
export declare const DEFAULT_FORGE_DOMAIN = "libp2p.direct"; | ||
export declare const DEFAULT_ACME_DIRECTORY = "https://acme-v02.api.letsencrypt.org/directory"; | ||
export declare const DEFAULT_PROVISION_TIMEOUT = 10000; | ||
export declare const DEFAULT_PROVISION_TIMEOUT = 120000; | ||
export declare const DEFAULT_PROVISION_REQUEST_TIMEOUT = 10000; | ||
export declare const DEFAULT_PROVISION_DELAY = 5000; | ||
@@ -12,2 +13,3 @@ export declare const DEFAULT_RENEWAL_THRESHOLD = 86400000; | ||
export declare const DEFAULT_CERTIFICATE_DATASTORE_KEY = "/libp2p/auto-tls/certificate"; | ||
export declare const DEFAULT_AUTO_CONFIRM_ADDRESS = false; | ||
//# sourceMappingURL=constants.d.ts.map |
export const DEFAULT_FORGE_ENDPOINT = 'https://registration.libp2p.direct'; | ||
export const DEFAULT_FORGE_DOMAIN = 'libp2p.direct'; | ||
export const DEFAULT_ACME_DIRECTORY = 'https://acme-v02.api.letsencrypt.org/directory'; | ||
export const DEFAULT_PROVISION_TIMEOUT = 10_000; | ||
export const DEFAULT_PROVISION_TIMEOUT = 120_000; | ||
export const DEFAULT_PROVISION_REQUEST_TIMEOUT = 10_000; | ||
export const DEFAULT_PROVISION_DELAY = 5_000; | ||
@@ -12,2 +13,3 @@ export const DEFAULT_RENEWAL_THRESHOLD = 86_400_000; | ||
export const DEFAULT_CERTIFICATE_DATASTORE_KEY = '/libp2p/auto-tls/certificate'; | ||
export const DEFAULT_AUTO_CONFIRM_ADDRESS = false; | ||
//# sourceMappingURL=constants.js.map |
@@ -10,2 +10,3 @@ import type { ComponentLogger, Libp2pEvents, TypedEventTarget } from '@libp2p/interface'; | ||
domain: string; | ||
autoConfirmAddress?: boolean; | ||
} | ||
@@ -18,2 +19,3 @@ export declare class DomainMapper { | ||
private readonly domain; | ||
private readonly autoConfirmAddress; | ||
private hasCertificate; | ||
@@ -20,0 +22,0 @@ constructor(components: DomainMapperComponents, init: DomainMapperInit); |
import { isIPv4, isIPv6 } from '@chainsafe/is-ip'; | ||
import { multiaddr } from '@multiformats/multiaddr'; | ||
import { getPublicIps } from './utils.js'; | ||
const MAX_DATE = 8_640_000_000_000_000; | ||
export class DomainMapper { | ||
@@ -9,8 +11,10 @@ log; | ||
domain; | ||
autoConfirmAddress; | ||
hasCertificate; | ||
constructor(components, init) { | ||
this.log = components.logger.forComponent('libp2p:certificate-manager:domain-mapper'); | ||
this.log = components.logger.forComponent('libp2p:auto-tls:domain-mapper'); | ||
this.addressManager = components.addressManager; | ||
this.events = components.events; | ||
this.domain = init.domain; | ||
this.autoConfirmAddress = init.autoConfirmAddress ?? false; | ||
this.mappedAddresses = new Set(); | ||
@@ -41,3 +45,4 @@ this.hasCertificate = false; | ||
updateMappings() { | ||
const publicIps = getPublicIps(this.addressManager.getAddresses()); | ||
const publicIps = getPublicIps(this.addressManager.getAddressesWithMetadata() | ||
.map(({ multiaddr }) => multiaddr)); | ||
// did our public IPs change? | ||
@@ -87,2 +92,9 @@ const addedIp4 = []; | ||
this.mappedAddresses.add(ip); | ||
if (this.autoConfirmAddress) { | ||
const ma = multiaddr(`/dns4/${domain}`); | ||
this.log('auto-confirming IP address %a', ma); | ||
this.addressManager.confirmObservedAddr(ma, { | ||
ttl: MAX_DATE - Date.now() | ||
}); | ||
} | ||
}); | ||
@@ -94,2 +106,9 @@ addedIp6.forEach(ip => { | ||
this.mappedAddresses.add(ip); | ||
if (this.autoConfirmAddress) { | ||
const ma = multiaddr(`/dns6/${domain}`); | ||
this.log('auto-confirming IP address %a', ma); | ||
this.addressManager.confirmObservedAddr(ma, { | ||
ttl: MAX_DATE - Date.now() | ||
}); | ||
} | ||
}); | ||
@@ -96,0 +115,0 @@ } |
@@ -100,6 +100,13 @@ /** | ||
* | ||
* @default 10000 | ||
* @default 120_000 | ||
*/ | ||
provisionTimeout?: number; | ||
/** | ||
* How long asking the forge endpoint to answer a DNS challenge can take | ||
* before we retry | ||
* | ||
* @default 10_000 | ||
*/ | ||
provisionRequestTimeout?: number; | ||
/** | ||
* Certificates are acquired when the `self:peer:update` event fires, which | ||
@@ -150,2 +157,12 @@ * happens when the node's addresses change. To avoid starting to map ports | ||
certificatePrivateKeyBits?: number; | ||
/** | ||
* Any mapped addresses are added to the observed address list. These | ||
* addresses require additional verification by the `@libp2p/autonat` protocol | ||
* or similar before they are trusted. | ||
* | ||
* To skip this verification and trust them immediately pass `true` here | ||
* | ||
* @default false | ||
*/ | ||
autoConfirmAddress?: boolean; | ||
} | ||
@@ -152,0 +169,0 @@ export interface AutoTLS { |
{ | ||
"name": "@libp2p/auto-tls", | ||
"version": "0.0.0-0862522fe", | ||
"version": "0.0.0-2625cc323", | ||
"description": "Automatically acquire a <peerId>.libp2p.direct TLS certificate", | ||
@@ -50,12 +50,14 @@ "license": "Apache-2.0 OR MIT", | ||
"@chainsafe/is-ip": "^2.0.2", | ||
"@libp2p/crypto": "5.0.7-0862522fe", | ||
"@libp2p/http-fetch": "^2.0.2", | ||
"@libp2p/interface": "2.2.1-0862522fe", | ||
"@libp2p/interface-internal": "2.1.1-0862522fe", | ||
"@libp2p/keychain": "5.0.10-0862522fe", | ||
"@libp2p/utils": "6.2.1-0862522fe", | ||
"@multiformats/multiaddr": "^12.3.1", | ||
"@multiformats/multiaddr-matcher": "^1.4.0", | ||
"@libp2p/crypto": "5.0.7-2625cc323", | ||
"@libp2p/http-fetch": "^2.1.0", | ||
"@libp2p/interface": "2.2.1-2625cc323", | ||
"@libp2p/interface-internal": "2.1.1-2625cc323", | ||
"@libp2p/keychain": "5.0.10-2625cc323", | ||
"@libp2p/utils": "6.2.1-2625cc323", | ||
"@multiformats/multiaddr": "^12.3.3", | ||
"@multiformats/multiaddr-matcher": "^1.6.0", | ||
"@peculiar/x509": "^1.12.3", | ||
"acme-client": "^5.4.0", | ||
"any-signal": "^4.1.1", | ||
"delay": "^6.0.0", | ||
"interface-datastore": "^8.3.1", | ||
@@ -66,7 +68,6 @@ "multiformats": "^13.3.1", | ||
"devDependencies": { | ||
"@libp2p/logger": "5.1.4-0862522fe", | ||
"@libp2p/peer-id": "5.0.8-0862522fe", | ||
"aegir": "^44.0.1", | ||
"@libp2p/logger": "5.1.4-2625cc323", | ||
"@libp2p/peer-id": "5.0.8-2625cc323", | ||
"aegir": "^45.0.5", | ||
"datastore-core": "^10.0.2", | ||
"delay": "^6.0.0", | ||
"p-event": "^6.0.1", | ||
@@ -73,0 +74,0 @@ "sinon": "^19.0.2", |
@@ -28,3 +28,3 @@ # @libp2p/auto-tls | ||
When a publicly dialable address is detected, use the p2p-forge service at | ||
<https://registration.libp2p.direct> to acquire a valid Let's Encrypted-backed | ||
<https://registration.libp2p.direct> to acquire a valid Let's Encrypt-backed | ||
TLS certificate, which the node can then use with the relevant transports. | ||
@@ -86,16 +86,8 @@ | ||
```console | ||
$ npm i @libp2p/plaintext | ||
$ npm i @libp2p/auto-tls | ||
``` | ||
## Browser `<script>` tag | ||
Loading this module through a script tag will make it's exports available as `Libp2pPlaintext` in the global namespace. | ||
```html | ||
<script src="https://unpkg.com/@libp2p/plaintext/dist/index.min.js"></script> | ||
``` | ||
# API Docs | ||
- <https://libp2p.github.io/js-libp2p/modules/_libp2p_plaintext.html> | ||
- <https://libp2p.github.io/js-libp2p/modules/_libp2p_auto_tls.html> | ||
@@ -106,4 +98,4 @@ # License | ||
- Apache 2.0, ([LICENSE-APACHE](https://github.com/libp2p/js-libp2p/blob/main/packages/connection-encrypter-plaintext/LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>) | ||
- MIT ([LICENSE-MIT](https://github.com/libp2p/js-libp2p/blob/main/packages/connection-encrypter-plaintext/LICENSE-MIT) / <http://opensource.org/licenses/MIT>) | ||
- Apache 2.0, ([LICENSE-APACHE](https://github.com/libp2p/js-libp2p/blob/main/packages/auto-tls/LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>) | ||
- MIT ([LICENSE-MIT](https://github.com/libp2p/js-libp2p/blob/main/packages/auto-tls/LICENSE-MIT) / <http://opensource.org/licenses/MIT>) | ||
@@ -110,0 +102,0 @@ # Contribution |
import { ClientAuth } from '@libp2p/http-fetch/auth' | ||
import { serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface' | ||
import { serviceCapabilities, serviceDependencies, setMaxListeners, start, stop } from '@libp2p/interface' | ||
import { debounce } from '@libp2p/utils/debounce' | ||
import { X509Certificate } from '@peculiar/x509' | ||
import * as acme from 'acme-client' | ||
import { anySignal } from 'any-signal' | ||
import delay from 'delay' | ||
import { Key } from 'interface-datastore' | ||
@@ -11,3 +13,3 @@ import { base36 } from 'multiformats/bases/base36' | ||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string' | ||
import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js' | ||
import { DEFAULT_ACCOUNT_PRIVATE_KEY_BITS, DEFAULT_ACCOUNT_PRIVATE_KEY_NAME, DEFAULT_ACME_DIRECTORY, DEFAULT_AUTO_CONFIRM_ADDRESS, DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME, DEFAULT_FORGE_DOMAIN, DEFAULT_FORGE_ENDPOINT, DEFAULT_PROVISION_DELAY, DEFAULT_PROVISION_REQUEST_TIMEOUT, DEFAULT_PROVISION_TIMEOUT, DEFAULT_RENEWAL_THRESHOLD } from './constants.js' | ||
import { DomainMapper } from './domain-mapper.js' | ||
@@ -23,2 +25,4 @@ import { createCsr, importFromPem, loadOrCreateKey, supportedAddressesFilter } from './utils.js' | ||
const RETRY_DELAY = 5_000 | ||
type CertificateEvent = 'certificate:provision' | 'certificate:renew' | ||
@@ -45,2 +49,3 @@ | ||
private readonly provisionTimeout: number | ||
private readonly provisionRequestTimeout: number | ||
private readonly renewThreshold: number | ||
@@ -61,5 +66,6 @@ private started: boolean | ||
private readonly domainMapper: DomainMapper | ||
private readonly autoConfirmAddress: boolean | ||
constructor (components: AutoTLSComponents, init: AutoTLSInit = {}) { | ||
this.log = components.logger.forComponent('libp2p:certificate-manager') | ||
this.log = components.logger.forComponent('libp2p:auto-tls') | ||
this.addressManager = components.addressManager | ||
@@ -75,2 +81,3 @@ this.privateKey = components.privateKey | ||
this.provisionTimeout = init.provisionTimeout ?? DEFAULT_PROVISION_TIMEOUT | ||
this.provisionRequestTimeout = init.provisionRequestTimeout ?? DEFAULT_PROVISION_REQUEST_TIMEOUT | ||
this.renewThreshold = init.renewThreshold ?? DEFAULT_RENEWAL_THRESHOLD | ||
@@ -82,2 +89,3 @@ this.accountPrivateKeyName = init.accountPrivateKeyName ?? DEFAULT_ACCOUNT_PRIVATE_KEY_NAME | ||
this.certificateDatastoreKey = init.certificateDatastoreKey ?? DEFAULT_CERTIFICATE_DATASTORE_KEY | ||
this.autoConfirmAddress = init.autoConfirmAddress ?? DEFAULT_AUTO_CONFIRM_ADDRESS | ||
this.clientAuth = new ClientAuth(this.privateKey) | ||
@@ -103,6 +111,12 @@ this.started = false | ||
get [serviceDependencies] (): string[] { | ||
return [ | ||
const dependencies = [ | ||
'@libp2p/identify', | ||
'@libp2p/keychain' | ||
] | ||
if (!this.autoConfirmAddress) { | ||
dependencies.push('@libp2p/autonat') | ||
} | ||
return dependencies | ||
} | ||
@@ -118,2 +132,3 @@ | ||
this.shutdownController = new AbortController() | ||
setMaxListeners(Infinity, this.shutdownController.signal) | ||
this.started = true | ||
@@ -131,3 +146,4 @@ } | ||
private _onSelfPeerUpdate (): void { | ||
const addresses = this.addressManager.getAddresses().filter(supportedAddressesFilter) | ||
const addresses = this.addressManager.getAddresses() | ||
.filter(supportedAddressesFilter) | ||
@@ -151,7 +167,25 @@ if (addresses.length === 0) { | ||
this.fetchCertificate(addresses, { | ||
signal: AbortSignal.timeout(this.provisionTimeout) | ||
Promise.resolve().then(async () => { | ||
let attempt = 0 | ||
while (true) { | ||
if (this.shutdownController?.signal.aborted === true) { | ||
throw this.shutdownController.signal.reason | ||
} | ||
try { | ||
await this.fetchCertificate(addresses, { | ||
signal: AbortSignal.timeout(this.provisionTimeout) | ||
}) | ||
return | ||
} catch (err) { | ||
this.log.error('provisioning certificate failed on attempt %d - %e', attempt++, err) | ||
} | ||
await delay(RETRY_DELAY) | ||
} | ||
}) | ||
.catch(err => { | ||
this.log.error('error fetching certificates - %e', err) | ||
this.log.error('giving up provisioning certificate - %e', err) | ||
}) | ||
@@ -166,4 +200,2 @@ .finally(() => { | ||
// TODO: handle rate limit errors like "too many new registrations (10) from this IP address in the last 3h0m0s, retry after 2024-11-01 09:22:38 UTC: see https://letsencrypt.org/docs/rate-limits/#new-registrations-per-ip-address" | ||
const certificatePrivateKey = await loadOrCreateKey(this.keychain, this.certificatePrivateKeyName, this.certificatePrivateKeyBits) | ||
@@ -204,3 +236,5 @@ const { pem, cert } = await this.loadOrCreateCertificate(certificatePrivateKey, multiaddrs, options) | ||
this.events.safeDispatchEvent(event, { | ||
detail: this.certificate | ||
detail: { | ||
...this.certificate | ||
} | ||
}) | ||
@@ -286,3 +320,29 @@ } | ||
challengeCreateFn: async (authz, challenge, keyAuthorization) => { | ||
await this.configureAcmeChallengeResponse(multiaddrs, keyAuthorization, options) | ||
const signal = anySignal([this.shutdownController?.signal, options?.signal]) | ||
setMaxListeners(Infinity, signal) | ||
let attempt = 0 | ||
while (true) { | ||
if (signal.aborted) { | ||
throw signal.reason | ||
} | ||
try { | ||
const timeout = AbortSignal.timeout(this.provisionRequestTimeout) | ||
const signal = anySignal([timeout, options?.signal]) | ||
setMaxListeners(Infinity, timeout, signal) | ||
await this.configureAcmeChallengeResponse(multiaddrs, keyAuthorization, { | ||
...options, | ||
signal | ||
}) | ||
return | ||
} catch (err: any) { | ||
this.log.error('contacting %s failed on attempt %d - %e', this.forgeEndpoint, attempt++, err.cause ?? err) | ||
} | ||
await delay(RETRY_DELAY) | ||
} | ||
}, | ||
@@ -301,4 +361,5 @@ challengeRemoveFn: async (authz, challenge, keyAuthorization) => { | ||
const endpoint = `${this.forgeEndpoint}v1/_acme-challenge` | ||
this.log('asking %sv1/_acme-challenge to respond to the acme DNS challenge on our behalf', endpoint) | ||
this.log('asking %s to respond to the acme DNS challenge on our behalf', endpoint) | ||
this.log('dialback public addresses: %s', addresses.join(', ')) | ||
const response = await this.clientAuth.authenticatedFetch(endpoint, { | ||
@@ -305,0 +366,0 @@ method: 'POST', |
export const DEFAULT_FORGE_ENDPOINT = 'https://registration.libp2p.direct' | ||
export const DEFAULT_FORGE_DOMAIN = 'libp2p.direct' | ||
export const DEFAULT_ACME_DIRECTORY = 'https://acme-v02.api.letsencrypt.org/directory' | ||
export const DEFAULT_PROVISION_TIMEOUT = 10_000 | ||
export const DEFAULT_PROVISION_TIMEOUT = 120_000 | ||
export const DEFAULT_PROVISION_REQUEST_TIMEOUT = 10_000 | ||
export const DEFAULT_PROVISION_DELAY = 5_000 | ||
@@ -12,1 +13,2 @@ export const DEFAULT_RENEWAL_THRESHOLD = 86_400_000 | ||
export const DEFAULT_CERTIFICATE_DATASTORE_KEY = '/libp2p/auto-tls/certificate' | ||
export const DEFAULT_AUTO_CONFIRM_ADDRESS = false |
import { isIPv4, isIPv6 } from '@chainsafe/is-ip' | ||
import { multiaddr } from '@multiformats/multiaddr' | ||
import { getPublicIps } from './utils.js' | ||
@@ -6,2 +7,4 @@ import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget } from '@libp2p/interface' | ||
const MAX_DATE = 8_640_000_000_000_000 | ||
export interface DomainMapperComponents { | ||
@@ -15,2 +18,3 @@ logger: ComponentLogger | ||
domain: string | ||
autoConfirmAddress?: boolean | ||
} | ||
@@ -24,9 +28,11 @@ | ||
private readonly domain: string | ||
private readonly autoConfirmAddress: boolean | ||
private hasCertificate: boolean | ||
constructor (components: DomainMapperComponents, init: DomainMapperInit) { | ||
this.log = components.logger.forComponent('libp2p:certificate-manager:domain-mapper') | ||
this.log = components.logger.forComponent('libp2p:auto-tls:domain-mapper') | ||
this.addressManager = components.addressManager | ||
this.events = components.events | ||
this.domain = init.domain | ||
this.autoConfirmAddress = init.autoConfirmAddress ?? false | ||
@@ -64,3 +70,6 @@ this.mappedAddresses = new Set() | ||
updateMappings (): void { | ||
const publicIps = getPublicIps(this.addressManager.getAddresses()) | ||
const publicIps = getPublicIps( | ||
this.addressManager.getAddressesWithMetadata() | ||
.map(({ multiaddr }) => multiaddr) | ||
) | ||
@@ -120,2 +129,10 @@ // did our public IPs change? | ||
this.mappedAddresses.add(ip) | ||
if (this.autoConfirmAddress) { | ||
const ma = multiaddr(`/dns4/${domain}`) | ||
this.log('auto-confirming IP address %a', ma) | ||
this.addressManager.confirmObservedAddr(ma, { | ||
ttl: MAX_DATE - Date.now() | ||
}) | ||
} | ||
}) | ||
@@ -128,2 +145,10 @@ | ||
this.mappedAddresses.add(ip) | ||
if (this.autoConfirmAddress) { | ||
const ma = multiaddr(`/dns6/${domain}`) | ||
this.log('auto-confirming IP address %a', ma) | ||
this.addressManager.confirmObservedAddr(ma, { | ||
ttl: MAX_DATE - Date.now() | ||
}) | ||
} | ||
}) | ||
@@ -130,0 +155,0 @@ } |
@@ -107,3 +107,3 @@ /** | ||
* | ||
* @default 10000 | ||
* @default 120_000 | ||
*/ | ||
@@ -113,2 +113,10 @@ provisionTimeout?: number | ||
/** | ||
* How long asking the forge endpoint to answer a DNS challenge can take | ||
* before we retry | ||
* | ||
* @default 10_000 | ||
*/ | ||
provisionRequestTimeout?: number | ||
/** | ||
* Certificates are acquired when the `self:peer:update` event fires, which | ||
@@ -165,2 +173,13 @@ * happens when the node's addresses change. To avoid starting to map ports | ||
certificatePrivateKeyBits?: number | ||
/** | ||
* Any mapped addresses are added to the observed address list. These | ||
* addresses require additional verification by the `@libp2p/autonat` protocol | ||
* or similar before they are trusted. | ||
* | ||
* To skip this verification and trust them immediately pass `true` here | ||
* | ||
* @default false | ||
*/ | ||
autoConfirmAddress?: boolean | ||
} | ||
@@ -167,0 +186,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
97958
7
0
100
1701
16
37
102
+ Addedany-signal@^4.1.1
+ Addeddelay@^6.0.0
+ Added@libp2p/crypto@5.0.7-2625cc323(transitive)
+ Added@libp2p/interface@2.2.1-2625cc323(transitive)
+ Added@libp2p/interface-internal@2.1.1-2625cc323(transitive)
+ Added@libp2p/keychain@5.0.10-2625cc323(transitive)
+ Added@libp2p/logger@5.1.4-2625cc323(transitive)
+ Added@libp2p/peer-collections@6.0.12-2625cc323(transitive)
+ Added@libp2p/peer-id@5.0.8-2625cc323(transitive)
+ Added@libp2p/utils@6.2.1-2625cc323(transitive)
- Removed@libp2p/crypto@5.0.7-0862522fe(transitive)
- Removed@libp2p/interface@2.2.1-0862522fe(transitive)
- Removed@libp2p/interface-internal@2.1.1-0862522fe(transitive)
- Removed@libp2p/keychain@5.0.10-0862522fe(transitive)
- Removed@libp2p/logger@5.1.4-0862522fe(transitive)
- Removed@libp2p/peer-collections@6.0.12-0862522fe(transitive)
- Removed@libp2p/peer-id@5.0.8-0862522fe(transitive)
- Removed@libp2p/utils@6.2.1-0862522fe(transitive)
Updated@libp2p/http-fetch@^2.1.0