diff --git a/packages/auto-tls/src/auto-tls.ts b/packages/auto-tls/src/auto-tls.ts index c15f539c8a..3e17d2b780 100644 --- a/packages/auto-tls/src/auto-tls.ts +++ b/packages/auto-tls/src/auto-tls.ts @@ -10,7 +10,7 @@ import { base36 } from 'multiformats/bases/base36' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 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_REQUEST_TIMEOUT, 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' import type { AutoTLSComponents, AutoTLSInit, AutoTLS as AutoTLSInterface } from './index.js' @@ -60,9 +60,10 @@ export class AutoTLS implements AutoTLSInterface { private readonly email private readonly domain 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 this.privateKey = components.privateKey this.peerId = components.peerId @@ -80,6 +81,7 @@ export class AutoTLS implements AutoTLSInterface { this.certificatePrivateKeyName = init.certificatePrivateKeyName ?? DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME this.certificatePrivateKeyBits = init.certificatePrivateKeyBits ?? DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS this.certificateDatastoreKey = init.certificateDatastoreKey ?? DEFAULT_CERTIFICATE_DATASTORE_KEY + this.autoConfirmAddress = init.autoConfirmAddress ?? DEFAULT_AUTO_CONFIRM_ADDRESS this.clientAuth = new ClientAuth(this.privateKey) this.started = false this.fetching = false @@ -100,10 +102,16 @@ export class AutoTLS implements AutoTLSInterface { ] get [serviceDependencies] (): string[] { - return [ + const dependencies = [ '@libp2p/identify', '@libp2p/keychain' ] + + if (!this.autoConfirmAddress) { + dependencies.push('@libp2p/autonat') + } + + return dependencies } async start (): Promise { diff --git a/packages/auto-tls/src/constants.ts b/packages/auto-tls/src/constants.ts index 8ef00fe699..89b9011d62 100644 --- a/packages/auto-tls/src/constants.ts +++ b/packages/auto-tls/src/constants.ts @@ -10,3 +10,4 @@ export const DEFAULT_ACCOUNT_PRIVATE_KEY_BITS = 2048 export const DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME = 'auto-tls-certificate-private-key' export const DEFAULT_CERTIFICATE_PRIVATE_KEY_BITS = 2048 export const DEFAULT_CERTIFICATE_DATASTORE_KEY = '/libp2p/auto-tls/certificate' +export const DEFAULT_AUTO_CONFIRM_ADDRESS = false diff --git a/packages/auto-tls/src/domain-mapper.ts b/packages/auto-tls/src/domain-mapper.ts index abc783dfe7..b7b92246db 100644 --- a/packages/auto-tls/src/domain-mapper.ts +++ b/packages/auto-tls/src/domain-mapper.ts @@ -1,8 +1,11 @@ import { isIPv4, isIPv6 } from '@chainsafe/is-ip' +import { multiaddr } from '@multiformats/multiaddr' import { getPublicIps } from './utils.js' import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget } from '@libp2p/interface' import type { AddressManager } from '@libp2p/interface-internal' +const MAX_DATE = 8_640_000_000_000_000 + export interface DomainMapperComponents { logger: ComponentLogger events: TypedEventTarget @@ -11,6 +14,7 @@ export interface DomainMapperComponents { export interface DomainMapperInit { domain: string + autoConfirmAddress?: boolean } export class DomainMapper { @@ -19,13 +23,15 @@ export class DomainMapper { private readonly events: TypedEventTarget private readonly mappedAddresses: Set 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 this.mappedAddresses = new Set() this.hasCertificate = false @@ -116,6 +122,14 @@ export class DomainMapper { this.log.trace('mapping IP %s to domain %s', ip, domain) this.addressManager.addDNSMapping(domain, [ip]) 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() + }) + } }) addedIp6.forEach(ip => { @@ -123,6 +137,14 @@ export class DomainMapper { this.log.trace('mapping IP %s to domain %s', ip, domain) this.addressManager.addDNSMapping(domain, [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() + }) + } }) } diff --git a/packages/auto-tls/src/index.ts b/packages/auto-tls/src/index.ts index 0bf440c27d..19180d05cd 100644 --- a/packages/auto-tls/src/index.ts +++ b/packages/auto-tls/src/index.ts @@ -168,6 +168,17 @@ export interface AutoTLSInit { * @default 2048 */ 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 } export interface AutoTLS { diff --git a/packages/auto-tls/test/domain-mapper.spec.ts b/packages/auto-tls/test/domain-mapper.spec.ts index 821f6472db..29335d9060 100644 --- a/packages/auto-tls/test/domain-mapper.spec.ts +++ b/packages/auto-tls/test/domain-mapper.spec.ts @@ -84,6 +84,47 @@ describe('domain-mapper', () => { ])).to.be.true() }) + it('should auto-confirm DNS mapping', async () => { + await stop(mapper) + mapper = new DomainMapper(components, { + domain: 'example.com', + autoConfirmAddress: true + }) + await start(mapper) + + const ip4 = '81.12.12.9' + const domain = '81-12-12-9.example.com' + + components.addressManager.getAddressesWithMetadata.returns([{ + multiaddr: multiaddr('/ip4/127.0.0.1/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr('/ip4/192.168.1.234/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'), + verified: true, + expires: Infinity, + type: 'transport' + }, { + multiaddr: multiaddr(`/ip4/${ip4}/tcp/1234/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN`), + verified: true, + expires: Infinity, + type: 'ip-mapping' + }]) + + components.events.safeDispatchEvent('certificate:provision', { + detail: { + key: importFromPem(PRIVATE_KEY_PEM), + cert: CERT + } + }) + + expect(components.addressManager.addDNSMapping.calledWith(domain, [ + ip4 + ])).to.be.true() + expect(components.addressManager.confirmObservedAddr.calledWith(multiaddr(`/dns4/${domain}`))).to.be.true() + }) + it('should update domain mapping on self peer update', () => { const ip4v1 = '81.12.12.9' const ip6v1 = '2001:4860:4860::8889'