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
```