Compare commits

...

59 commits

Author SHA1 Message Date
2afc318a1c Cari file su "/" 2025-03-05 19:40:36 +08:00
a2f1791575 Aggiorna README.md 2025-03-05 19:38:34 +08:00
6eccb27db8 Aggiorna README.md 2025-03-05 19:37:48 +08:00
helgeerbe
918b4bff8e Merge branch 'master' of https://github.com/helgeerbe/certbot-dns-ionos 2024-11-09 17:11:16 +01:00
helgeerbe
5ce3289390 update dependency certbot >= 3.0.0 2024-11-09 17:04:21 +01:00
helgeerbe
60183e735a
Update README.md 2024-10-20 16:18:05 +02:00
helgeerbe
91b8c5c030 build: prepare version 2024.10.20 2024-10-20 16:10:33 +02:00
helgeerbe
d7bf47e77c fix: set long_description_content_type to text/markdown
This error breaks the upload to pypi
2024-10-20 16:03:17 +02:00
helgeerbe
90c955d4cd
Update python-publish.yml 2024-10-19 17:57:48 +02:00
helgeerbe
b48a3ad55a
Update python-publish.yml 2024-10-19 17:55:12 +02:00
helgeerbe
badb2fdb5b
Update python-publish.yml 2024-10-19 17:52:39 +02:00
helgeerbe
7df7370596
Update python-publish.yml 2024-10-19 17:48:18 +02:00
helgeerbe
4a927e2276 Merge branch 'pr/DorianCoding/27' 2024-10-19 17:11:09 +02:00
helgeerbe
1c69320b67 keep certbot 2.11.0 2024-10-19 17:07:18 +02:00
helgeerbe
6abb7a767e
Update README.rst 2024-10-18 08:48:10 +02:00
helgeerbe
e56a4f2ccf
Merge pull request #25 from zak905:add_reference_to_ionos_cloud
Add reference to ionos cloud
2024-10-17 15:59:17 +02:00
DorianCoding
894ec02c6a
Certbot 3.0 update 2024-10-17 07:54:10 +02:00
DorianCoding
0e93705e19
Typo error 2024-10-15 13:17:52 +02:00
DorianCoding
f1cca97647
Change of version 2024-10-15 13:09:14 +02:00
DorianCoding
9f20694196
Update README.md 2024-10-15 13:08:56 +02:00
DorianCoding
cbd3045a82
Update dns_ionos.py 2024-10-15 13:03:20 +02:00
DorianCoding
8d34261163
Update and rename README.rst to README.md 2024-10-14 20:35:08 +02:00
DorianCoding
fd7a3f5b6c
Build corrections 2024-10-14 20:24:52 +02:00
DorianCoding
62232e8ae3
Snap test 2024-10-13 17:06:13 +02:00
zak905
a8335dc4f4
delete repeated word 2024-04-17 11:18:04 +02:00
zak905
2e1c71c52a
update text and links 2024-04-15 14:54:52 +02:00
zak905
4a137e2e72
fix horizontal line break 2024-04-15 13:02:37 +02:00
zak905
4295966943
add reference to IONOS cloud certbot plugin 2024-04-15 13:00:43 +02:00
helgeerbe
eb3809a416
Update python-publish.yml 2024-01-08 14:10:59 +01:00
helgeerbe
38489d2e02
Update python-publish.yml 2024-01-08 13:08:48 +01:00
helgeerbe
452f2f9d99
Update python-publish.yml 2024-01-08 13:02:06 +01:00
helgeerbe
1cf78a6457
Update python-publish.yml 2024-01-08 12:58:08 +01:00
helgeerbe
d816ebd145 Prepare new release 2024.01.08 2024-01-08 10:59:02 +01:00
helgeerbe
755421b079
Merge pull request #23 from ich01000/patch-1
Update README.rst
2024-01-08 10:47:06 +01:00
ich01000
0279c75c02
Update README.rst
Add Link to IONOS control panel and reference between credentials.ini and  domain.tld.ini
2023-12-24 03:33:18 +01:00
helgeerbe
bb89121fbb update version number 2023-11-13 13:17:44 +01:00
helgeerbe
cbc886306f
Update README.rst 2023-11-13 13:02:00 +01:00
helgeerbe
5f657fa45b update readme.rst for 2023.11.13 2023-11-13 13:00:08 +01:00
helgeerbe
8c19deacf7
Merge pull request #22 from LondonPaul/subdomain_fix
Fix managed zone lookup to ensure correct domain is selected where th…
2023-11-13 12:54:37 +01:00
Paul Morgan
4e1a3cc19e Fix managed zone lookup to ensure correct domain is selected where there are two domains with the same ending e.g. example.com and thisisanexample.com 2023-11-11 12:44:35 +00:00
helgeerbe
9913d7a1a1 fix in readme 2022-11-24 15:49:12 +01:00
helgeerbe
d448498407
Update python-publish.yml 2022-11-24 15:02:25 +01:00
helgeerbe
bc5182814b
Update python-publish.yml 2022-11-24 15:00:56 +01:00
helgeerbe
94b8817623 Merge branch 'dev' 2022-11-24 14:48:50 +01:00
helgeerbe
1eede89442 Change Development Status on
master branch to Stable
2022-11-24 14:46:58 +01:00
helgeerbe
43b053ef22 Make plugin compliant for certbot 2.x 2022-11-24 14:38:19 +01:00
helgeerbe
ee3af96543
Merge pull request #20 from alexzorin/remove-zope
remove zope to fix compatibility with Certbot 2.x
2022-11-24 13:47:54 +01:00
helgeerbe
ad110403e0 Merge remote-tracking branch 'origin/master' into dev 2022-11-24 13:42:00 +01:00
helgeerbe
2ab6e04cff change test environment 2022-11-24 13:25:35 +01:00
Alex Zorin
c12f443c15 remove zope to fix compatibility with Certbot 2.x 2022-11-23 07:16:45 +11:00
Helge
b4b3f24e1e Changelog in readme 2022-05-15 15:02:44 +02:00
Helge
3976e00bf9 New release 2022.05.15 2022-05-15 15:00:17 +02:00
helgeerbe
fd9e833d52
Merge pull request #16 from helgeerbe/dev
Added capability to handle multiple domain validations
2022-05-15 14:56:45 +02:00
helgeerbe
9924b74aba
Update README.rst
Correct file permissions.
2021-11-23 13:16:08 +01:00
helgeerbe
e4c4755271 typos in debug messages 2021-10-07 14:59:02 +02:00
helgeerbe
f32190139b on error response content is array with 1 element 2021-10-07 10:35:45 +02:00
helgeerbe
f3808bf952
Merge pull request #11 from PackeTsar/master
Add ability to authenticate multiple domains
2021-10-06 15:32:28 +02:00
John W Kerns
33c2b41737 Added capability to handle multiple domain validations 2021-09-28 18:42:23 -07:00
John W Kerns
f1c855927d More logging 2021-09-28 16:12:56 -07:00
11 changed files with 593 additions and 222 deletions

View file

@ -6,26 +6,33 @@ name: Upload Python Package
on: on:
release: release:
types: [created] types: [created]
workflow_dispatch:
jobs: jobs:
deploy: build-n-publish:
name: Build and publish to PyPi
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/certbot-dns-ionos
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
contents: write
steps: steps:
- uses: actions/checkout@v2 - name: Checkout source
uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: '3.x' python-version: '3.12'
- name: Install dependencies
- name: Build source and wheel distributions
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip build twine
pip install setuptools wheel twine python -m build
- name: Build and publish twine check --strict dist/*
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - name: Publish package distributions to PyPI
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} uses: pypa/gh-action-pypi-publish@release/v1
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
#Snap
certbot-dns-ionos_20240108_amd64.snap
#snap-constraints.txt
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,python # Created by https://www.toptal.com/developers/gitignore/api/windows,visualstudiocode,python

4
.vscode/launch.json vendored
View file

@ -8,9 +8,9 @@
"name": "Python: Current File", "name": "Python: Current File",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"program": "/home/pi/dev/certbot/venv3/bin/certbot", "program": ".venv/bin/certbot",
"console": "integratedTerminal", "console": "integratedTerminal",
"args": ["certonly", "-a", "dns-ionos", "-d", "*.erbehome.de", "--dns-ionos-credentials", "/home/pi/dev/certbot-dns-ionos/my_debug/secrets/credentials.ini", "--config-dir", "my_debug/config", "--work-dir", "my_debug/work", "--logs-dir", "my_debug/logs"] "args": ["certonly", "-a", "dns-ionos", "-d", "*.erbehome.de", "-d", "erbehome.de", "--dns-ionos-credentials", "my_debug/secrets/credentials.ini", "--config-dir", "my_debug/config", "--work-dir", "my_debug/work", "--logs-dir", "my_debug/logs"]
} }
] ]
} }

207
README.md Normal file
View file

@ -0,0 +1,207 @@
# certbot-dns-ionos
In order to create a docker container with a certbot-dns-ionos installation,
create an empty directory with the following ``Dockerfile``:
```docker
FROM certbot/certbot
RUN pip install certbot-dns-ionos
```
Proceed to build the image
```docker
docker build -t certbot/dns-ionos .
```
if not exit make dir and set as root to secure the folder
```bash
mkdir -p /etc/letsencrypt/.secrets
chown root:root /etc/letsencrypt/.secrets
chmod 700 /etc/letsencrypt/.secrets
```
insert the APY KEY of IONOS in patachina.it.ini and copy
```bash
sudo cp patachina.it.ini /etc/letsencrypt/.secrets
```
Once that's finished, the application can be run as follows::
```docker
docker run --rm \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /etc/letsencrypt:/etc/letsencrypt \
--cap-drop=all \
certbot/dns-ionos certonly \
--authenticator dns-ionos \
--dns-ionos-propagation-seconds 900 \
--dns-ionos-credentials \
/etc/letsencrypt/.secrets/patachina.it.ini \
--no-self-upgrade \
--keep-until-expiring --non-interactive --expand \
--server https://acme-v02.api.letsencrypt.org/directory \
-d patachina.it -d '*.patachina.it'
```
## Original git
[IONOS](https://www.ionos.de/) DNS Authenticator plugin for [Certbot](https://certbot.eff.org/)
![Ionos](https://www.ionos.co.uk/newsroom/wp-content/uploads/sites/7/2021/12/LOGO_IONOS_Blue_RGB-1.png)
This plugin automates the process of completing a ``dns-01`` challenge by
creating, and subsequently removing, TXT records using the [IONOS Remote API](https://developer.hosting.ionos.com/docs/dns).
## Configuration of IONOS
In the `System -> Remote Users` you have to have a user, with the following rights
- Client Functions
- DNS zone functions
- DNS txt functions
## Installation
### Snap
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/certbot-dns-ionos)
Snap version will be compatible with certbot 3.0. Thanks to [DorianCoding](https://github.com/DorianCoding) to make this plugin avalabe in the Snap Store.
### Pip
`pip install certbot-dns-ionos`
## Named Arguments
To start using DNS authentication for ionos, pass the following arguments on certbot's command line:
| Command args | Command definition |
| --- | --- |
|``--authenticator dns-ionos`` | select the authenticator plugin (Required) |
|``--dns-ionos-credentials`` |ionos Remote User credentials INI file. (Required) |
|``--dns-ionos-propagation-seconds``|waiting time for DNS to propagate before asking the ACME server to verify the DNS record. (Default: 10, Recommended: 60) |
## Credentials
An example ``credentials.ini`` file:
```ini
dns_ionos_prefix = myapikeyprefix
dns_ionos_secret = verysecureapikeysecret
dns_ionos_endpoint = https://api.hosting.ionos.com
```
The key can be managed under the following link: <https://developer.hosting.ionos.de/?source=IonosControlPanel>
The path to this file can be provided interactively or using the
`--dns-ionos-credentials` command-line argument. Certbot
records the path to this file for use during renewal, but does not store the file's contents.
> [!CAUTION]
> You should protect these API credentials as you would the
password to your ionos account. Users who can read this file can use these credentials to issue arbitrary API calls
on your behalf. Users who can cause Certbot to run using these credentials can complete a ``dns-01`` challenge
to acquire new certificates or revoke existing certificates for associated domains, even if those domains aren't
being managed by this server.
> [!WARNING]
> Certbot will emit a warning if it detects that the credentials file can be accessed by other users on your system.
The warning reads "Unsafe permissions on credentials configuration file", followed by the path to the
credentials file. This warning will be emitted each time Certbot uses the credentials file, including for renewal,
and cannot be silenced except by addressing the issue (e.g., by using a command like ``chmod 600`` to
restrict access to the file and ``chmod 700`` to restrict access to the folder).
## Examples
To acquire a single certificate for both ``example.com`` and
``*.example.com``, waiting 900 seconds for DNS propagation:
```bash
certbot certonly \
--authenticator dns-ionos \
--dns-ionos-credentials /etc/letsencrypt/.secrets/domain.tld.ini \
--dns-ionos-propagation-seconds 900 \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos \
--rsa-key-size 4096 \
-d 'example.com' \
-d '*.example.com'
```
## Docker
In order to create a docker container with a certbot-dns-ionos installation,
create an empty directory with the following ``Dockerfile``:
```docker
FROM certbot/certbot
RUN pip install certbot-dns-ionos
```
Proceed to build the image
```docker
docker build -t certbot/dns-ionos .
```
Once that's finished, the application can be run as follows::
```docker
docker run --rm \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /etc/letsencrypt:/etc/letsencrypt \
--cap-drop=all \
certbot/dns-ionos certonly \
--authenticator dns-ionos \
--dns-ionos-propagation-seconds 900 \
--dns-ionos-credentials \
/etc/letsencrypt/.secrets/domain.tld.ini \
--no-self-upgrade \
--keep-until-expiring --non-interactive --expand \
--server https://acme-v02.api.letsencrypt.org/directory \
-d example.com -d '*.example.com'
```
It is suggested to secure the folder as follows
```bash
chown root:root /etc/letsencrypt/.secrets
chmod 700 /etc/letsencrypt/.secrets
```
The file 'domain.tld.ini' must be replaced with the version of the example 'credentials.ini' adapted to your provider.
## Changelog
- 2024.11.09
- Update for Certbot 3.0.0
- 2024.10.20
- fix: set long_description_content_type to text/markdown.
This error breaks the upload to pypi
- 2024.10.19
- Update for Certbot 2.11.0
- Update README.md, changed from README.rst
- Addition of a snap
- Correction in case of API error
- 2024.01.08
- Update README.rst
- Add Link to IONOS control panel and reference between credentials.ini and domain.tld.ini
- 2023.11.13
- Fix managed zone lookup to ensure correct domain is selected where there are two domains with the same ending e.g. example.com and thisisanexample.com (PR #22)
- 2022.11.24
- Remove zope to fix compatibility with Certbot 2.x (Fixes #19)
- As a reminder, Certbot will default to issuing ECDSA certificates from release 2.0.0.
- If you update from a prior certbot release, run the plugin once manually. You will be prompted to update RSA key type to ECDSA.
- 2022.05.15
- Added capability to handle multiple domain validations #16
- 2021.09.20.post1
- Fix version number
- 2021.09.20
- Fix #9 Domain not known when using subdomain
## Related Plugins
It's important to note that this plugin targets [IONOS Developer DNS API](https://developer.hosting.ionos.com/docs/dns>).
If you are using IONOS [Cloud DNS service](https://cloud.ionos.com/network/cloud-dns>),
there is a different plugin provided by IONOS: <https://github.com/ionos-cloud/certbot-dns-ionos-cloud>

View file

@ -1,143 +0,0 @@
certbot-dns-ionos
=====================
IONOS_ DNS Authenticator plugin for Certbot_
This plugin automates the process of completing a ``dns-01`` challenge by
creating, and subsequently removing, TXT records using the IONOS Remote API.
Configuration of IONOS
---------------------------
In the `System -> Remote Users` you have to have a user, with the following rights
- Client Functions
- DNS zone functions
- DNS txt functions
.. _IONOS: https://www.ionos.de/
.. _Certbot: https://certbot.eff.org/
Installation
------------
::
pip install certbot-dns-ionos
Named Arguments
---------------
To start using DNS authentication for ionos, pass the following arguments on
certbot's command line:
=============================================== ===============================================
``--authenticator dns-ionos`` select the authenticator plugin (Required)
``--dns-ionos-credentials`` ionos Remote User credentials
INI file. (Required)
``--dns-ionos-propagation-seconds`` waiting time for DNS to propagate before asking
the ACME server to verify the DNS record.
(Default: 10, Recommended: >= 600)
=============================================== ===============================================
Credentials
-----------
An example ``credentials.ini`` file:
.. code-block:: ini
dns_ionos_prefix = myapikeyprefix
dns_ionos_secret = verysecureapikeysecret
dns_ionos_endpoint = https://api.hosting.ionos.com
The path to this file can be provided interactively or using the
``--dns-ionos-credentials`` command-line argument. Certbot
records the path to this file for use during renewal, but does not store the
file's contents.
**CAUTION:** You should protect these API credentials as you would the
password to your ionos account. Users who can read this file can use these
credentials to issue arbitrary API calls on your behalf. Users who can cause
Certbot to run using these credentials can complete a ``dns-01`` challenge to
acquire new certificates or revoke existing certificates for associated
domains, even if those domains aren't being managed by this server.
Certbot will emit a warning if it detects that the credentials file can be
accessed by other users on your system. The warning reads "Unsafe permissions
on credentials configuration file", followed by the path to the credentials
file. This warning will be emitted each time Certbot uses the credentials file,
including for renewal, and cannot be silenced except by addressing the issue
(e.g., by using a command like ``chmod 600`` to restrict access to the file).
Examples
--------
To acquire a single certificate for both ``example.com`` and
``*.example.com``, waiting 900 seconds for DNS propagation:
.. code-block:: bash
certbot certonly \
--authenticator dns-ionos \
--dns-ionos-credentials /etc/letsencrypt/.secrets/domain.tld.ini \
--dns-ionos-propagation-seconds 900 \
--server https://acme-v02.api.letsencrypt.org/directory \
--agree-tos \
--rsa-key-size 4096 \
-d 'example.com' \
-d '*.example.com'
Docker
------
In order to create a docker container with a certbot-dns-ionos installation,
create an empty directory with the following ``Dockerfile``:
.. code-block:: docker
FROM certbot/certbot
RUN pip install certbot-dns-ionos
Proceed to build the image::
docker build -t certbot/dns-ionos .
Once that's finished, the application can be run as follows::
docker run --rm \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /etc/letsencrypt:/etc/letsencrypt \
--cap-drop=all \
certbot/dns-ionos certonly \
--authenticator dns-ionos \
--dns-ionos-propagation-seconds 900 \
--dns-ionos-credentials \
/etc/letsencrypt/.secrets/domain.tld.ini \
--no-self-upgrade \
--keep-until-expiring --non-interactive --expand \
--server https://acme-v02.api.letsencrypt.org/directory \
-d example.com -d '*.example.com'
It is suggested to secure the folder as follows::
chown root:root /etc/letsencrypt/.secrets
chmod 600 /etc/letsencrypt/.secrets
Changelog
=========
- 2021.09.20.post1
- Fix version number
- 2021.09.20
- Fix #9 Domain not known when using subdomain

View file

@ -1,20 +1,15 @@
"""DNS Authenticator for IONOS.""" """DNS Authenticator for IONOS."""
import json import json
import logging import logging
import time
import requests import requests
import zope.interface
from certbot import errors from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common from certbot.plugins import dns_common
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IAuthenticator)
@zope.interface.provider(interfaces.IPluginFactory)
class Authenticator(dns_common.DNSAuthenticator): class Authenticator(dns_common.DNSAuthenticator):
"""DNS Authenticator for IONOS """DNS Authenticator for IONOS
@ -31,7 +26,7 @@ class Authenticator(dns_common.DNSAuthenticator):
@classmethod @classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments( super(Authenticator, cls).add_parser_arguments(
add, default_propagation_seconds=120 add, default_propagation_seconds=10
) )
add("credentials", help="IONOS credentials INI file.") add("credentials", help="IONOS credentials INI file.")
@ -53,13 +48,14 @@ class Authenticator(dns_common.DNSAuthenticator):
) )
def _perform(self, domain, validation_name, validation): def _perform(self, domain, validation_name, validation):
logger.debug(f"_perform called with: domain: {domain}, validation_name: {validation_name}, validation: {validation}")
self._get_ionos_client().add_txt_record( self._get_ionos_client().add_txt_record(
domain, validation_name, validation, self.ttl domain, validation_name, validation, self.ttl
) )
def _cleanup(self, domain, validation_name, validation): def _cleanup(self, domain, validation_name, validation):
self._get_ionos_client().del_txt_record( self._get_ionos_client().del_matching_records(
domain, validation_name, validation, self.ttl domain, validation_name
) )
def _get_ionos_client(self): def _get_ionos_client(self):
@ -101,7 +97,7 @@ class _ionosClient(object):
# is a subdomain # is a subdomain
for zone in zones: for zone in zones:
# get the zone id # get the zone id
if domain.endswith(zone['name']): if domain.endswith(f".{zone['name']}"):
return zone['id'], zone['name'] return zone['id'], zone['name']
return None, None return None, None
@ -124,12 +120,13 @@ class _ionosClient(object):
raise errors.PluginError( raise errors.PluginError(
"HTTP Error during request. Unknown type {0}".format(type) "HTTP Error during request. Unknown type {0}".format(type)
) )
logger.debug("API REquest to URL: %s", url) logger.debug("API request to URL: %s", url)
if resp.status_code != 200: if resp.status_code != 200:
content = json.loads(resp.content) content = json.loads(resp.content) # on error content is array with 1 element
error_msg = resp.reason + " " + content['message'] error_msg = "" if content['message'] is None else content['message']
raise errors.PluginError( raise errors.PluginError(
"HTTP Error during request {0}:{1}".format(resp.status_code, error_msg) "HTTP Error during request {0}({1}): {2}".format(
resp.reason, resp.status_code, error_msg)
) )
result = None result = None
if type == 'get': if type == 'get':
@ -164,43 +161,15 @@ class _ionosClient(object):
logger.info("already there, id {0}".format(id)) logger.info("already there, id {0}".format(id))
return return
else: else:
logger.info("update txt record") logger.info("adding additional record")
self._update_txt_record( entries = self.clean_entries(self.get_existing_records(zone_id, record_name))
zone_id, id, record_content, record_ttl self.add_additional_record(
zone_id, record_name, record_content, record_ttl, entries
) )
else: else:
logger.info("insert new txt record") logger.info("insert new txt record")
self._insert_txt_record(zone_id, record_name, record_content, record_ttl) self._insert_txt_record(zone_id, record_name, record_content, record_ttl)
def del_txt_record(self, domain, record_name, record_content, record_ttl):
"""
Delete a TXT record using the supplied information.
:param str domain: The domain to use to look up the managed zone.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the IONOS API
"""
zone_id, zone_name = self._find_managed_zone_id(domain)
if zone_id is None:
raise errors.PluginError("Domain not known")
logger.debug("domain found: %s with id: %s", zone_name, zone_id)
content, id = self.get_existing_txt(zone_id, record_name)
if content is not None:
if content == record_content:
logger.debug("delete TXT record: %s", id)
self._delete_txt_record(zone_id, id)
def _update_txt_record(self, zone_id, primary_id, record_content, record_ttl):
data = {}
data['disabled'] = False
data['content'] = record_content
data['ttl'] = record_ttl
data['prio'] = 0
logger.debug("update with data: %s", data)
self._api_request(type='put', action='/dns/v1/zones/{0}/records/{1}'.format(zone_id,primary_id), data=data)
def _insert_txt_record(self, zone_id, record_name, record_content, record_ttl): def _insert_txt_record(self, zone_id, record_name, record_content, record_ttl):
data = {} data = {}
data['disabled'] = False data['disabled'] = False
@ -244,3 +213,61 @@ class _ionosClient(object):
content = content.rstrip('\"') content = content.rstrip('\"')
return content, entry["id"] return content, entry["id"]
return None, None return None, None
def get_existing_records(self, zone_id, record_name):
"""
Pull a list of existing TXT records with the record_name
"""
zone_data = self._api_request(type='get', action='/dns/v1/zones/{0}'.format(zone_id))
results = []
for entry in zone_data['records']:
if entry["name"] == record_name and entry["type"] == "TXT":
results.append(entry)
return results
def clean_entries(self, entries):
"""
Clean up existing DNS entries to prepare to write them back to the API
by only including certain keys and cleaning up the content.
"""
results = []
for entry in entries:
results.append({
'name': entry['name'],
'type': entry['type'],
'content': entry['content'].replace('"', ''), # Strip double-quotes
'ttl': entry['ttl'],
'disabled': entry['disabled'],
})
return results
def add_additional_record(self, zone_id, record_name, record_content, record_ttl, existing_records):
"""
Add another TXT record with the record_name but with new content. This
is done to allow multiple domains to be validated at the same time.
existing_records is a list of existing records since we need to issue
a PATCH and include the existing records.
"""
data = {}
data['disabled'] = False
data['type'] = 'TXT'
data['name'] = record_name
data['content'] = record_content
data['ttl'] = record_ttl
data['prio'] = 0
existing_records.append(data)
logger.debug("insert with data: %s", existing_records)
self._api_request(type='patch', action='/dns/v1/zones/{0}'.format(zone_id), data=existing_records)
def del_matching_records(self, domain, record_name):
"""
Deletes any TXT records with matching record_name. Loops through all
records with that name and deletes them.
"""
zone_id, zone_name = self._find_managed_zone_id(domain)
if zone_id is None:
raise errors.PluginError("Domain not known")
logger.debug("domain found: %s with id: %s", zone_name, zone_id)
entries = self.get_existing_records(zone_id, record_name)
for entry in entries:
self._delete_txt_record(zone_id, entry['id'])

3
patachina.it.ini Normal file
View file

@ -0,0 +1,3 @@
dns_ionos_prefix = 855b5080c2434ffc99f23fa20f09f0aa
dns_ionos_secret = bcD1lRr5af4UuXUGRSVTj-9uQxrxcj9GKcHo8D3xtaSducnWNxGx35XwqjXOwOSvTO7apFUjDWzbApUShMKPzA
dns_ionos_endpoint = https://api.hosting.ionos.com

View file

@ -1,11 +1,11 @@
from setuptools import setup from setuptools import setup
from setuptools import find_packages from setuptools import find_packages
version = "2021.09.20.post1" version = '2024.11.09'
install_requires = [ install_requires = [
"acme>=1.8.0", "acme>=3.0.0",
"certbot>=0.31.0", "certbot>=3.0.0",
"setuptools", "setuptools",
"requests", "requests",
"mock", "mock",
@ -16,7 +16,7 @@ install_requires = [
from os import path from os import path
this_directory = path.abspath(path.dirname(__file__)) this_directory = path.abspath(path.dirname(__file__))
with open(path.join(this_directory, "README.rst")) as f: with open(path.join(this_directory, "README.md")) as f:
long_description = f.read() long_description = f.read()
setup( setup(
@ -24,26 +24,25 @@ setup(
version=version, version=version,
description="IONOS DNS Authenticator plugin for Certbot", description="IONOS DNS Authenticator plugin for Certbot",
long_description=long_description, long_description=long_description,
long_description_content_type="text/x-rst", long_description_content_type="text/markdown",
url="https://github.com/helgeerbe/certbot-dns-ionos", url="https://github.com/helgeerbe/certbot-dns-ionos",
author="Helge Erbe", author="Helge Erbe",
author_email="helge@erbehome.de", author_email="helge@erbehome.de",
license="Apache License 2.0", license="Apache License 2.0",
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", python_requires=">=3.8",
classifiers=[ classifiers=[
"Development Status :: 3 - Alpha", "Development Status :: 5 - Production/Stable",
"Environment :: Plugins", "Environment :: Plugins",
"Intended Audience :: System Administrators", "Intended Audience :: System Administrators",
"License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux", "Operating System :: POSIX :: Linux",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP",
"Topic :: Security", "Topic :: Security",
"Topic :: System :: Installation/Setup", "Topic :: System :: Installation/Setup",

189
snap-constraints.txt Normal file
View file

@ -0,0 +1,189 @@
# This file was generated by tools/pinning/current/repin.sh and can be updated using
# that script.
#
# It is normally used as constraints to pip, however, it has the name
# requirements.txt so that is scanned by GitHub. See
# https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems
# for more info.
alabaster==0.7.13 ; python_version >= "3.8" and python_version < "4.0"
apacheconfig==0.3.2 ; python_version >= "3.8" and python_version < "4.0"
appnope==0.1.4 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "darwin"
astroid==3.0.3 ; python_version >= "3.8" and python_version < "4.0"
asttokens==2.4.1 ; python_version >= "3.8" and python_version < "4.0"
attrs==24.2.0 ; python_version >= "3.8" and python_version < "4.0"
azure-core==1.30.2 ; python_version >= "3.8" and python_version < "4.0"
azure-devops==7.1.0b4 ; python_version >= "3.8" and python_version < "4.0"
babel==2.16.0 ; python_version >= "3.8" and python_version < "4.0"
backcall==0.2.0 ; python_version >= "3.8" and python_version < "4.0"
bcrypt==4.2.0 ; python_version >= "3.8" and python_version < "4.0"
beautifulsoup4==4.12.3 ; python_version >= "3.8" and python_version < "4.0"
boto3==1.35.6 ; python_version >= "3.8" and python_version < "4.0"
botocore==1.35.6 ; python_version >= "3.8" and python_version < "4.0"
build==1.2.1 ; python_version >= "3.8" and python_version < "4.0"
cachecontrol==0.14.0 ; python_version >= "3.8" and python_version < "4.0"
cachetools==5.5.0 ; python_version >= "3.8" and python_version < "4.0"
certifi==2024.7.4 ; python_version >= "3.8" and python_version < "4.0"
cffi==1.17.0 ; python_version >= "3.8" and python_version < "4.0"
chardet==5.2.0 ; python_version >= "3.8" and python_version < "4.0"
charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "4.0"
cleo==2.1.0 ; python_version >= "3.8" and python_version < "4.0"
cloudflare==2.19.4 ; python_version >= "3.8" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.8" and python_version < "4.0"
configargparse==1.7 ; python_version >= "3.8" and python_version < "4.0"
configobj==5.0.8 ; python_version >= "3.8" and python_version < "4.0"
coverage==7.6.1 ; python_version >= "3.8" and python_version < "4.0"
crashtest==0.4.1 ; python_version >= "3.8" and python_version < "4.0"
cryptography==43.0.0 ; python_version >= "3.8" and python_version < "4.0"
cython==0.29.37 ; python_version >= "3.8" and python_version < "4.0"
decorator==5.1.1 ; python_version >= "3.8" and python_version < "4.0"
deprecated==1.2.14 ; python_version >= "3.8" and python_version < "4.0"
dill==0.3.8 ; python_version >= "3.8" and python_version < "4.0"
distlib==0.3.8 ; python_version >= "3.8" and python_version < "4.0"
distro==1.9.0 ; python_version >= "3.8" and python_version < "4.0"
dns-lexicon==3.18.0 ; python_version >= "3.8" and python_version < "4.0"
dnspython==2.6.1 ; python_version >= "3.8" and python_version < "4.0"
docutils==0.20.1 ; python_version >= "3.8" and python_version < "4.0"
dulwich==0.21.7 ; python_version >= "3.8" and python_version < "4.0"
exceptiongroup==1.2.2 ; python_version >= "3.8" and python_version < "3.11"
execnet==2.1.1 ; python_version >= "3.8" and python_version < "4.0"
executing==2.0.1 ; python_version >= "3.8" and python_version < "4.0"
fabric==3.2.2 ; python_version >= "3.8" and python_version < "4.0"
fastjsonschema==2.20.0 ; python_version >= "3.8" and python_version < "4.0"
filelock==3.15.4 ; python_version >= "3.8" and python_version < "4.0"
google-api-core==2.19.1 ; python_version >= "3.8" and python_version < "4.0"
google-api-python-client==2.142.0 ; python_version >= "3.8" and python_version < "4.0"
google-auth-httplib2==0.2.0 ; python_version >= "3.8" and python_version < "4.0"
google-auth==2.34.0 ; python_version >= "3.8" and python_version < "4.0"
googleapis-common-protos==1.64.0 ; python_version >= "3.8" and python_version < "4.0"
httplib2==0.22.0 ; python_version >= "3.8" and python_version < "4.0"
idna==3.8 ; python_version >= "3.8" and python_version < "4.0"
imagesize==1.4.1 ; python_version >= "3.8" and python_version < "4.0"
importlib-metadata==8.4.0 ; python_version >= "3.8" and python_version < "4.0"
importlib-resources==6.4.4 ; python_version >= "3.8" and python_version < "4.0"
iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
installer==0.7.0 ; python_version >= "3.8" and python_version < "4.0"
invoke==2.2.0 ; python_version >= "3.8" and python_version < "4.0"
ipdb==0.13.13 ; python_version >= "3.8" and python_version < "4.0"
ipython==8.12.3 ; python_version >= "3.8" and python_version < "4.0"
isodate==0.6.1 ; python_version >= "3.8" and python_version < "4.0"
isort==5.13.2 ; python_version >= "3.8" and python_version < "4.0"
jaraco-classes==3.4.0 ; python_version >= "3.8" and python_version < "4.0"
jedi==0.19.1 ; python_version >= "3.8" and python_version < "4.0"
jeepney==0.8.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "linux"
jinja2==3.1.4 ; python_version >= "3.8" and python_version < "4.0"
jmespath==1.0.1 ; python_version >= "3.8" and python_version < "4.0"
josepy==1.14.0 ; python_version >= "3.8" and python_version < "4.0"
jsonlines==4.0.0 ; python_version >= "3.8" and python_version < "4.0"
jsonpickle==3.2.2 ; python_version >= "3.8" and python_version < "4.0"
keyring==24.3.1 ; python_version >= "3.8" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.8" and python_version < "4.0"
markupsafe==2.1.5 ; python_version >= "3.8" and python_version < "4.0"
matplotlib-inline==0.1.7 ; python_version >= "3.8" and python_version < "4.0"
mccabe==0.7.0 ; python_version >= "3.8" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.8" and python_version < "4.0"
more-itertools==10.4.0 ; python_version >= "3.8" and python_version < "4.0"
msgpack==1.0.8 ; python_version >= "3.8" and python_version < "4.0"
msrest==0.7.1 ; python_version >= "3.8" and python_version < "4.0"
mypy-extensions==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
mypy==1.9.0 ; python_version >= "3.8" and python_version < "4.0"
nh3==0.2.18 ; python_version >= "3.8" and python_version < "4.0"
oauthlib==3.2.2 ; python_version >= "3.8" and python_version < "4.0"
packaging==24.1 ; python_version >= "3.8" and python_version < "4.0"
paramiko==3.4.1 ; python_version >= "3.8" and python_version < "4.0"
parsedatetime==2.6 ; python_version >= "3.8" and python_version < "4.0"
parso==0.8.4 ; python_version >= "3.8" and python_version < "4.0"
pexpect==4.9.0 ; python_version >= "3.8" and python_version < "4.0"
pickleshare==0.7.5 ; python_version >= "3.8" and python_version < "4.0"
pip==24.2 ; python_version >= "3.8" and python_version < "4.0"
pkginfo==1.10.0 ; python_version >= "3.8" and python_version < "4.0"
platformdirs==4.2.2 ; python_version >= "3.8" and python_version < "4.0"
pluggy==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
ply==3.11 ; python_version >= "3.8" and python_version < "4.0"
poetry-core==1.9.0 ; python_version >= "3.8" and python_version < "4.0"
poetry-plugin-export==1.8.0 ; python_version >= "3.8" and python_version < "4.0"
poetry==1.8.3 ; python_version >= "3.8" and python_version < "4.0"
prompt-toolkit==3.0.47 ; python_version >= "3.8" and python_version < "4.0"
proto-plus==1.24.0 ; python_version >= "3.8" and python_version < "4.0"
protobuf==5.27.3 ; python_version >= "3.8" and python_version < "4.0"
ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "4.0"
pure-eval==0.2.3 ; python_version >= "3.8" and python_version < "4.0"
pyasn1-modules==0.4.0 ; python_version >= "3.8" and python_version < "4.0"
pyasn1==0.6.0 ; python_version >= "3.8" and python_version < "4.0"
pycparser==2.22 ; python_version >= "3.8" and python_version < "4.0"
pygments==2.18.0 ; python_version >= "3.8" and python_version < "4.0"
pylint==3.0.2 ; python_version >= "3.8" and python_version < "4.0"
pynacl==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
pynsist==2.7 ; python_version >= "3.8" and python_version < "4.0"
pyopenssl==24.2.1 ; python_version >= "3.8" and python_version < "4.0"
pyotp==2.9.0 ; python_version >= "3.8" and python_version < "4.0"
pyparsing==3.1.4 ; python_version >= "3.8" and python_version < "4.0"
pyproject-api==1.7.1 ; python_version >= "3.8" and python_version < "4.0"
pyproject-hooks==1.1.0 ; python_version >= "3.8" and python_version < "4.0"
pyrfc3339==1.1 ; python_version >= "3.8" and python_version < "4.0"
pytest-cov==5.0.0 ; python_version >= "3.8" and python_version < "4.0"
pytest-xdist==3.6.1 ; python_version >= "3.8" and python_version < "4.0"
pytest==8.3.2 ; python_version >= "3.8" and python_version < "4.0"
python-augeas==1.1.0 ; python_version >= "3.8" and python_version < "4.0"
python-dateutil==2.9.0.post0 ; python_version >= "3.8" and python_version < "4.0"
python-digitalocean==1.17.0 ; python_version >= "3.8" and python_version < "4.0"
pytz==2024.1 ; python_version >= "3.8" and python_version < "4.0"
pywin32-ctypes==0.2.3 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32"
pywin32==306 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "win32"
pyyaml==6.0.2 ; python_version >= "3.8" and python_version < "4.0"
rapidfuzz==3.9.6 ; python_version >= "3.8" and python_version < "4.0"
readme-renderer==43.0 ; python_version >= "3.8" and python_version < "4.0"
requests-download==0.1.2 ; python_version >= "3.8" and python_version < "4.0"
requests-file==2.1.0 ; python_version >= "3.8" and python_version < "4.0"
requests-oauthlib==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
requests-toolbelt==1.0.0 ; python_version >= "3.8" and python_version < "4.0"
requests==2.32.3 ; python_version >= "3.8" and python_version < "4.0"
rfc3986==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
rich==13.8.0 ; python_version >= "3.8" and python_version < "4.0"
rsa==4.9 ; python_version >= "3.8" and python_version < "4"
s3transfer==0.10.2 ; python_version >= "3.8" and python_version < "4.0"
secretstorage==3.3.3 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "linux"
semantic-version==2.10.0 ; python_version >= "3.8" and python_version < "4.0"
setuptools-rust==1.10.1 ; python_version >= "3.8" and python_version < "4.0"
setuptools==73.0.1 ; python_version >= "3.8" and python_version < "4.0"
shellingham==1.5.4 ; python_version >= "3.8" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
snowballstemmer==2.2.0 ; python_version >= "3.8" and python_version < "4.0"
soupsieve==2.6 ; python_version >= "3.8" and python_version < "4.0"
sphinx-rtd-theme==2.0.0 ; python_version >= "3.8" and python_version < "4.0"
sphinx==7.1.2 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-applehelp==1.0.4 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-htmlhelp==2.0.1 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-jquery==4.1 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.8" and python_version < "4.0"
sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.8" and python_version < "4.0"
stack-data==0.6.3 ; python_version >= "3.8" and python_version < "4.0"
tldextract==5.1.2 ; python_version >= "3.8" and python_version < "4.0"
tomli==2.0.1 ; python_version >= "3.8" and python_full_version <= "3.11.0a6"
tomlkit==0.13.2 ; python_version >= "3.8" and python_version < "4.0"
tox==4.18.0 ; python_version >= "3.8" and python_version < "4.0"
traitlets==5.14.3 ; python_version >= "3.8" and python_version < "4.0"
trove-classifiers==2024.7.2 ; python_version >= "3.8" and python_version < "4.0"
twine==5.1.1 ; python_version >= "3.8" and python_version < "4.0"
types-cffi==1.16.0.20240331 ; python_version >= "3.8" and python_version < "4.0"
types-httplib2==0.22.0.20240310 ; python_version >= "3.8" and python_version < "4.0"
types-pyopenssl==24.1.0.20240722 ; python_version >= "3.8" and python_version < "4.0"
types-pyrfc3339==1.1.1.5 ; python_version >= "3.8" and python_version < "4.0"
types-python-dateutil==2.9.0.20240821 ; python_version >= "3.8" and python_version < "4.0"
types-pytz==2024.1.0.20240417 ; python_version >= "3.8" and python_version < "4.0"
types-pywin32==306.0.0.20240822 ; python_version >= "3.8" and python_version < "4.0"
types-requests==2.31.0.6 ; python_version >= "3.8" and python_version < "4.0"
types-setuptools==73.0.0.20240822 ; python_version >= "3.8" and python_version < "4.0"
types-six==1.16.21.20240513 ; python_version >= "3.8" and python_version < "4.0"
types-urllib3==1.26.25.14 ; python_version >= "3.8" and python_version < "4.0"
typing-extensions==4.12.2 ; python_version >= "3.8" and python_version < "4.0"
uritemplate==4.1.1 ; python_version >= "3.8" and python_version < "4.0"
urllib3==1.26.19 ; python_version >= "3.8" and python_version < "4.0"
virtualenv==20.26.3 ; python_version >= "3.8" and python_version < "4.0"
wcwidth==0.2.13 ; python_version >= "3.8" and python_version < "4.0"
wheel==0.44.0 ; python_version >= "3.8" and python_version < "4.0"
wrapt==1.16.0 ; python_version >= "3.8" and python_version < "4.0"
xattr==1.1.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform == "darwin"
yarg==0.1.10 ; python_version >= "3.8" and python_version < "4.0"
zipp==3.20.1 ; python_version >= "3.8" and python_version < "4.0"

21
snap/hooks/post-refresh Normal file
View file

@ -0,0 +1,21 @@
#!/bin/sh -e
# This file is generated automatically and should not be edited manually.
# get certbot version
if [ ! -f "$SNAP/certbot-shared/certbot-version.txt" ]; then
echo "No certbot version available; not doing version comparison check" >> "$SNAP_DATA/debuglog"
exit 0
fi
cb_installed=$(cat $SNAP/certbot-shared/certbot-version.txt)
# get required certbot version for plugin. certbot version must be at least the plugin's
# version. note that this is not the required version in setup.py, but the version number itself.
cb_required=$(grep -oP "version = '\K.*(?=')" $SNAP/setup.py)
$SNAP/bin/python3 -c "import sys; from packaging import version; sys.exit(1) if version.parse('$cb_installed') < version.parse('$cb_required') else sys.exit(0)" || exit_code=$?
if [ "$exit_code" -eq 1 ]; then
echo "Certbot is version $cb_installed but needs to be at least $cb_required before" \
"this plugin can be updated; will try again on next refresh."
exit 1
fi

58
snap/snapcraft.yaml Normal file
View file

@ -0,0 +1,58 @@
# This file is generated automatically and should not be edited manually.
name: certbot-dns-ionos
summary: IONOS DNS Authenticator plugin for Certbot
description: IONOS DNS Authenticator plugin for Certbot
confinement: strict
grade: stable
base: core24
adopt-info: certbot-dns-ionos
parts:
certbot-dns-ionos:
plugin: python
source: .
override-pull: |
craftctl default
craftctl set version=$(grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]")
build-environment:
# We set this environment variable while building to try and increase the
# stability of fetching the rust crates needed to build the cryptography
# library.
- CARGO_NET_GIT_FETCH_WITH_CLI: "true"
# Constraints are passed through the environment variable PIP_CONSTRAINTS instead of using the
# parts.[part_name].constraints option available in snapcraft.yaml when the Python plugin is
# used. This is done to let these constraints be applied not only on the certbot package
# build, but also on any isolated build that pip could trigger when building wheels for
# dependencies. See https://github.com/certbot/certbot/pull/8443 for more info.
- PIP_CONSTRAINT: $SNAPCRAFT_PART_SRC/snap-constraints.txt
- SNAP_BUILD: "True"
# To build cryptography and cffi if needed
build-packages:
- gcc
- git
- build-essential
- libssl-dev
- libffi-dev
- python3-dev
- cargo
- pkg-config
certbot-metadata:
plugin: dump
source: .
stage: [setup.py, certbot-shared]
override-pull: |
craftctl default
mkdir -p $SNAPCRAFT_PART_SRC/certbot-shared
slots:
certbot:
interface: content
content: certbot-1
read:
- $SNAP/lib/python3.12/site-packages
plugs:
certbot-metadata:
interface: content
content: metadata-1
target: $SNAP/certbot-shared