or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

crypto-utilities.mddisplay-ui.mderror-handling.mdindex.mdmain-config.mdplugin-development.mdutility-functions.md

plugin-development.mddocs/

0

# Plugin Development

1

2

Plugin interfaces and base classes for developing custom authenticator and installer plugins to extend Certbot's functionality with new challenge types and web server support.

3

4

## Capabilities

5

6

### Base Plugin Interface

7

8

All Certbot plugins must implement the base Plugin interface, which provides common functionality and metadata.

9

10

```python { .api }

11

class Plugin(metaclass=ABCMeta):

12

"""

13

Base interface for all Certbot plugins.

14

15

Objects providing this interface are called without satisfying

16

entry point "extras" dependencies, so make sure they're importable

17

without extras.

18

"""

19

20

description: str # Short plugin description

21

name: str # Unique name of the plugin

22

23

def __init__(self, config: Optional[NamespaceConfig], name: str):

24

"""

25

Create a new Plugin.

26

27

Args:

28

config: Configuration object

29

name: Unique plugin name

30

"""

31

32

@classmethod

33

def add_parser_arguments(cls, add: Callable[..., None]):

34

"""

35

Add plugin-specific command line arguments.

36

37

Args:

38

add: Function to add arguments to parser

39

"""

40

41

def prepare(self):

42

"""

43

Prepare the plugin for use.

44

45

Perform any necessary initialization before the plugin

46

is used for authentication or installation.

47

"""

48

49

def more_info(self) -> str:

50

"""

51

Return additional information about the plugin.

52

53

Returns:

54

Human-readable string with more details

55

"""

56

```

57

58

### Authenticator Interface

59

60

Authenticator plugins handle ACME challenges to prove domain ownership.

61

62

```python { .api }

63

class Authenticator(Plugin):

64

"""

65

Interface for ACME challenge authenticator plugins.

66

67

Authenticators prove control of domains through various challenge types

68

like HTTP-01, DNS-01, or TLS-ALPN-01.

69

"""

70

71

def get_chall_pref(self, domain: str) -> Iterable[type[Challenge]]:

72

"""

73

Return iterable of challenge preferences for domain.

74

75

Args:

76

domain: Domain name for which to get preferences

77

78

Returns:

79

Iterable of preferred challenge types (Challenge subclasses) in order of preference

80

"""

81

82

def perform(self, achalls: list[AnnotatedChallenge]) -> list[ChallengeResponse]:

83

"""

84

Perform the given challenge.

85

86

Args:

87

achalls: List of annotated challenges to perform

88

89

Returns:

90

List of challenge responses

91

92

Raises:

93

errors.PluginError: If challenges cannot be performed

94

"""

95

96

def cleanup(self, achalls: list[AnnotatedChallenge]):

97

"""

98

Clean up after challenge completion.

99

100

Args:

101

achalls: List of annotated challenges to clean up

102

103

Raises:

104

errors.PluginError: If cleanup fails

105

"""

106

```

107

108

### Installer Interface

109

110

Installer plugins deploy certificates to web servers and apply security enhancements.

111

112

```python { .api }

113

class Installer(Plugin):

114

"""

115

Interface for certificate installer plugins.

116

117

Installers deploy certificates to web servers and can apply

118

enhancements like HTTPS redirects.

119

"""

120

121

def get_all_names(self) -> Iterable[str]:

122

"""

123

Get all domain names that the installer can handle.

124

125

Returns:

126

Iterable of domain names found in server configuration

127

"""

128

129

def deploy_cert(self, domain: str, cert_path: str, key_path: str,

130

chain_path: str, fullchain_path: str):

131

"""

132

Deploy certificate for domain.

133

134

Args:

135

domain: Domain name for certificate

136

cert_path: Path to certificate file

137

key_path: Path to private key file

138

chain_path: Path to certificate chain file

139

fullchain_path: Path to full certificate chain file

140

141

Raises:

142

errors.PluginError: If deployment fails

143

"""

144

145

def enhance(self, domain: str, enhancement: str,

146

options: Optional[Union[list[str], str]] = None):

147

"""

148

Apply enhancement to domain configuration.

149

150

Args:

151

domain: Domain name to enhance

152

enhancement: Type of enhancement (e.g., 'redirect')

153

options: Enhancement-specific options

154

155

Raises:

156

errors.PluginError: If enhancement fails

157

"""

158

159

def supported_enhancements(self) -> list[str]:

160

"""

161

Get list of supported enhancements.

162

163

Returns:

164

List of enhancement names supported by this installer

165

"""

166

167

def save(self, title: Optional[str] = None, temporary: bool = False):

168

"""

169

Save current configuration state.

170

171

Args:

172

title: Title for the save operation

173

temporary: Whether this is a temporary save

174

175

Raises:

176

errors.PluginError: If save fails

177

"""

178

179

def rollback_checkpoints(self, rollback: int = 1):

180

"""

181

Rollback configuration to previous state.

182

183

Args:

184

rollback: Number of checkpoints to rollback

185

186

Raises:

187

errors.PluginError: If rollback fails

188

"""

189

190

def recovery_routine(self):

191

"""

192

Revert configuration to most recent finalized checkpoint.

193

194

Remove all changes (temporary and permanent) that have not been

195

finalized. Useful to protect against crashes and interruptions.

196

197

Raises:

198

errors.PluginError: If unable to recover the configuration

199

"""

200

201

def config_test(self):

202

"""

203

Test current configuration for validity.

204

205

Raises:

206

errors.MisconfigurationError: If configuration is invalid

207

"""

208

209

def restart(self):

210

"""

211

Restart the server to apply configuration changes.

212

213

Raises:

214

errors.MisconfigurationError: If restart fails

215

"""

216

```

217

218

### Plugin Base Classes

219

220

Common base classes that provide shared functionality for plugin implementations.

221

222

```python { .api }

223

class Plugin(interfaces.Plugin):

224

"""

225

Base implementation class for plugins with common functionality.

226

227

Provides default implementations and utility methods.

228

"""

229

230

def __init__(self, config: NamespaceConfig, name: str):

231

"""Initialize plugin with configuration."""

232

self.config = config

233

self.name = name

234

235

class Installer(Plugin, interfaces.Installer):

236

"""

237

Base class for installer plugins with common functionality.

238

239

Provides shared installer methods and utilities.

240

"""

241

242

def get_all_names_answer(self, question: str) -> set[str]:

243

"""

244

Get domain names with user interaction.

245

246

Args:

247

question: Question to ask user about domain selection

248

249

Returns:

250

Set of selected domain names

251

"""

252

```

253

254

## Plugin Registration

255

256

### Entry Points

257

258

Plugins are registered through setuptools entry points in pyproject.toml or setup.py:

259

260

```python

261

# In pyproject.toml:

262

[project.entry-points."certbot.plugins"]

263

my-authenticator = "my_package.plugin:MyAuthenticator"

264

my-installer = "my_package.plugin:MyInstaller"

265

266

# In setup.py:

267

entry_points={

268

'certbot.plugins': [

269

'my-authenticator = my_package.plugin:MyAuthenticator',

270

'my-installer = my_package.plugin:MyInstaller',

271

],

272

}

273

```

274

275

### Built-in Plugins

276

277

Certbot includes several built-in plugins:

278

279

- **manual**: Manual authenticator for custom verification

280

- **standalone**: Standalone authenticator with built-in web server

281

- **webroot**: Webroot authenticator using existing web server

282

- **null**: Null installer that performs no installation

283

284

## Example Plugin Implementation

285

286

```python

287

from certbot import interfaces, errors

288

from certbot.plugins.common import Plugin

289

from certbot import configuration

290

from typing import Optional, Iterable, Union

291

292

class CustomAuthenticator(Plugin, interfaces.Authenticator):

293

"""Custom authenticator plugin example."""

294

295

description = "Custom DNS authenticator"

296

name = "custom-dns"

297

298

def __init__(self, config: Optional[configuration.NamespaceConfig], name: str):

299

super().__init__(config, name)

300

self.credentials = None

301

302

@classmethod

303

def add_parser_arguments(cls, add):

304

add('credentials', help='Path to credentials file')

305

306

def prepare(self):

307

"""Prepare the authenticator."""

308

# Load credentials, validate configuration, etc.

309

if not self.config.credentials:

310

raise errors.PluginError("Credentials file not specified")

311

312

def perform(self, achalls):

313

"""Perform DNS challenges."""

314

responses = []

315

for achall in achalls:

316

# Implement DNS record creation logic

317

self._create_dns_record(achall.domain, achall.validation)

318

response = achall.response_and_validation(self.account_key)

319

responses.append(response)

320

return responses

321

322

def cleanup(self, achalls):

323

"""Clean up DNS records."""

324

for achall in achalls:

325

self._delete_dns_record(achall.domain)

326

327

def _create_dns_record(self, domain: str, validation: str):

328

"""Create DNS TXT record for validation."""

329

# Implement DNS provider-specific logic

330

pass

331

332

def _delete_dns_record(self, domain: str):

333

"""Delete DNS TXT record."""

334

# Implement DNS provider-specific cleanup

335

pass

336

337

class CustomInstaller(Plugin, interfaces.Installer):

338

"""Custom installer plugin example."""

339

340

description = "Custom web server installer"

341

name = "custom-server"

342

343

def get_all_names(self) -> Iterable[str]:

344

"""Get all domain names from server configuration."""

345

# Parse server configuration files

346

return ['example.com', 'www.example.com']

347

348

def deploy_cert(self, domain: str, cert_path: str, key_path: str,

349

chain_path: str, fullchain_path: str):

350

"""Deploy certificate to server."""

351

# Update server configuration with certificate paths

352

pass

353

354

def enhance(self, domain: str, enhancement: str,

355

options: Optional[Union[list[str], str]] = None):

356

"""Apply enhancements like HTTPS redirect."""

357

if enhancement == 'redirect':

358

# Implement HTTPS redirect configuration

359

pass

360

361

def supported_enhancements(self) -> list[str]:

362

"""Return list of supported enhancements."""

363

return ['redirect', 'hsts']

364

365

def save(self, title: Optional[str] = None, temporary: bool = False):

366

"""Save server configuration."""

367

# Write configuration files

368

pass

369

370

def config_test(self):

371

"""Test server configuration."""

372

# Validate configuration syntax

373

pass

374

375

def restart(self):

376

"""Restart the server."""

377

# Restart server process

378

pass

379

```

380

381

## Additional Interfaces

382

383

### Account Storage Interface

384

385

Interface for managing ACME account storage and retrieval.

386

387

```python { .api }

388

class AccountStorage(metaclass=ABCMeta):

389

"""

390

Interface for ACME account storage implementations.

391

392

Provides methods for finding, loading, and saving ACME accounts.

393

"""

394

395

def find_all(self) -> list[Account]:

396

"""

397

Find all stored accounts.

398

399

Returns:

400

List of all Account objects found in storage

401

"""

402

403

def load(self, account_id: str) -> Account:

404

"""

405

Load an account by its identifier.

406

407

Args:

408

account_id: Unique identifier for the account

409

410

Returns:

411

Account object for the specified ID

412

413

Raises:

414

errors.AccountNotFound: If account cannot be found

415

errors.AccountStorageError: If account cannot be loaded

416

"""

417

418

def save(self, account: Account, client: ClientV2) -> None:

419

"""

420

Save account to storage.

421

422

Args:

423

account: Account object to save

424

client: ACME client associated with account

425

426

Raises:

427

errors.AccountStorageError: If account cannot be saved

428

"""

429

```

430

431

### Renewable Certificate Interface

432

433

Interface representing a certificate lineage that can be renewed.

434

435

```python { .api }

436

class RenewableCert(metaclass=ABCMeta):

437

"""

438

Interface to a certificate lineage for renewal operations.

439

440

Provides access to certificate paths and metadata.

441

"""

442

443

@property

444

def cert_path(self) -> str:

445

"""Path to the certificate file."""

446

447

@property

448

def key_path(self) -> str:

449

"""Path to the private key file."""

450

451

@property

452

def chain_path(self) -> str:

453

"""Path to the certificate chain file."""

454

455

@property

456

def fullchain_path(self) -> str:

457

"""Path to the full chain file (cert + chain)."""

458

459

@property

460

def lineagename(self) -> str:

461

"""Name given to the certificate lineage."""

462

463

def names(self) -> list[str]:

464

"""

465

Get subject names of this certificate.

466

467

Returns:

468

List of domain names in the certificate

469

470

Raises:

471

errors.CertStorageError: If certificate file cannot be read

472

"""

473

```