pax_global_header00006660000000000000000000000064143444115340014515gustar00rootroot0000000000000052 comment=bfa77f2a7b50541b4fac2fdaa1ba142c48455f6d certbot-plugin-gandi-1.4.3/000077500000000000000000000000001434441153400155405ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/.gitignore000066400000000000000000000000441434441153400175260ustar00rootroot00000000000000*.pyc *.pyo *.swp *.swo *.egg-info certbot-plugin-gandi-1.4.3/LICENSE000066400000000000000000000020741434441153400165500ustar00rootroot00000000000000MIT License Copyright (c) 2018 Michael Porter, Yohann Leon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. certbot-plugin-gandi-1.4.3/README.md000066400000000000000000000124101434441153400170150ustar00rootroot00000000000000# Certbot plugin for authentication using Gandi LiveDNS This is a plugin for [Certbot](https://certbot.eff.org/) that uses the Gandi LiveDNS API to allow [Gandi](https://www.gandi.net/) customers to prove control of a domain name. ## Usage > /!\ Certbot 1.7.0 imposed breaking changes on this plugin, make sure to remove any prefix-based configuration 1. Obtain a Gandi API token (see [Gandi LiveDNS API](https://doc.livedns.gandi.net/)) 2. Install the plugin using `pip install certbot-plugin-gandi` 3. Create a `gandi.ini` config file with the following contents and apply `chmod 600 gandi.ini` on it: ``` # live dns v5 api key dns_gandi_api_key=APIKEY # optional organization id, remove it if not used dns_gandi_sharing_id=SHARINGID ``` Replace `APIKEY` with your Gandi API key and ensure permissions are set to disallow access to other users. 4. Run `certbot` and direct it to use the plugin for authentication and to use the config file previously created: ``` certbot certonly --authenticator dns-gandi --dns-gandi-credentials /etc/letsencrypt/gandi/gandi.ini -d domain.com ``` Add additional options as required to specify an installation plugin etc. Please note that this solution is usually not relevant if you're using Gandi's web hosting services as Gandi offers free automated certificates for all simplehosting plans having SSL in the admin interface. Be aware that the plugin configuration must be provided by CLI, configuration for third-party plugins in `cli.ini` is not supported by certbot for the moment. Please refer to [#4351](https://github.com/certbot/certbot/issues/4351), [#6504](https://github.com/certbot/certbot/issues/6504) and [#7681](https://github.com/certbot/certbot/issues/7681) for details. ## Distribution PyPI is the upstream distribution channel, other channels are not maintained by me. * PyPI: https://pypi.org/project/certbot-plugin-gandi/ * Archlinux: https://aur.archlinux.org/packages/certbot-dns-gandi-git/ * Debian: https://packages.debian.org/sid/main/python3-certbot-dns-gandi * Ubuntu: https://packages.ubuntu.com/kinetic/python3-certbot-dns-gandi * Snap: Not yet packaged. I'm lazy. Latests builds are also available on Launchpad: https://launchpad.net/ubuntu/+source/python-certbot-dns-gandi Be careful, installing this plugin with PyPI will also install certbot via PyPI which may conflict with any other certbot already installed on your system. ## Wildcard certificates This plugin is particularly useful when you need to obtain a wildcard certificate using dns challenges: ``` certbot certonly --authenticator dns-gandi --dns-gandi-credentials /etc/letsencrypt/gandi/gandi.ini -d domain.com -d \*.domain.com --server https://acme-v02.api.letsencrypt.org/directory ``` ## Automatic renewal You can setup automatic renewal using `crontab` with the following job for weekly renewal attempts: ``` 0 0 * * 0 certbot renew -q --authenticator dns-gandi --dns-gandi-credentials /etc/letsencrypt/gandi/gandi.ini --server https://acme-v02.api.letsencrypt.org/directory ``` ## Reading material * A [blog post](https://www.linux.it/~ema/posts/letsencrypt-the-manual-plugin-is-not-working/) by [@realEmaRocca](https://twitter.com/realEmaRocca) describing how to use this plugin on Debian ## FAQ > I have a warning telling me `Plugin legacy name certbot-plugin-gandi:dns may be removed in a future version. Please use dns instead.` Certbot had moved to remove 3rd party plugins prefixes since v1.7.0. Please switch to the new configuration format and remove any used prefix-based configuration. For the time being, you can still use prefixes, but if you do so and keep using prefix-based cli arguments, stay consistent and use prefix-based configuration in the ini file. #### New post-prefix configuration for certbot>=1.7.0 * `--authenticator dns-gandi --dns-gandi-credentials` * `gandi.ini` ``` # live dns v5 api key dns_gandi_api_key=APIKEY # optional organization id, remove it if not used # if you use certbot<1.7.0 please use certbot_plugin_gandi:dns_sharing_id=SHARINGID dns_gandi_sharing_id=SHARINGID ``` #### Legacy prefix-based configuration for certbot<1.7.0 * `-a certbot-plugin-gandi:dns --certbot-plugin-gandi:dns-credentials` * `gandi.ini` ``` # live dns v5 api key certbot_plugin_gandi:dns_api_key=APIKEY # optional organization id, remove it if not used certbot_plugin_gandi:dns_sharing_id=SHARINGID ``` See [certbot/8131](https://github.com/certbot/certbot/pull/8131) and [certbot-plugin-gandi/23](https://github.com/obynio/certbot-plugin-gandi/issues/23) for details. Please make sure to update the configuration file to the new format. > I get a `Property "certbot_plugin_gandi:dns_api_key" not found (should be API key for Gandi account).. Skipping.` See above. > Why do you keep this plugin a third-party plugin ? Just merge it with certbot ? This Gandi plugin is a third party plugin mainly because this plugin is not officially backed by Gandi and because Certbot [does not accept](https://certbot.eff.org/docs/contributing.html?highlight=propagation#writing-your-own-plugin) new plugin submissions. ![no_submission](https://user-images.githubusercontent.com/2095991/101479748-fd9da280-3952-11eb-884f-491470718f4d.png) ## Credits Huge thanks to Michael Porter for its [original work](https://gitlab.com/sudoliyang/certbot-plugin-gandi) ! certbot-plugin-gandi-1.4.3/certbot_plugin_gandi/000077500000000000000000000000001434441153400217225ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/certbot_plugin_gandi/__init__.py000066400000000000000000000000001434441153400240210ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/certbot_plugin_gandi/gandi_api.py000066400000000000000000000062371434441153400242170ustar00rootroot00000000000000import requests import six from collections import namedtuple from certbot.plugins import dns_common _GandiConfig = namedtuple('_GandiConfig', ('api_key', 'sharing_id',)) _BaseDomain = namedtuple('_BaseDomain', ('fqdn')) def get_config(api_key, sharing_id): return _GandiConfig(api_key=api_key, sharing_id=sharing_id) def _get_json(response): try: data = response.json() except ValueError: return dict() return data def _get_response_message(response, default=''): return _get_json(response).get('message', default) def _headers(cfg): return { 'Content-Type': 'application/json', 'Authorization': 'Apikey ' + cfg.api_key } def _get_url(*segs): return 'https://api.gandi.net/v5/livedns/{}'.format('/'.join(segs)) def _request(cfg, method, segs, **kw): headers = _headers(cfg) url = _get_url(*segs) return requests.request(method, url, headers=headers, params={'sharing_id': cfg.sharing_id}, **kw) def _get_base_domain(cfg, domain): for candidate_base_domain in dns_common.base_domain_name_guesses(domain): response = _request(cfg, 'GET', ('domains', candidate_base_domain)) if response.ok: data = _get_json(response) fqdn = data.get('fqdn') if fqdn: return _BaseDomain(fqdn=fqdn) return None def _get_relative_name(base_domain, name): suffix = '.' + base_domain.fqdn return name[:-len(suffix)] if name.endswith(suffix) else None def _get_txt_record(cfg, base_domain, relative_name): response = _request(cfg, 'GET', ['domains', base_domain.fqdn, 'records', relative_name, 'TXT']) if not response.ok: return [] data = _get_json(response) vals = data.get('rrset_values') if vals: return vals else: return [] def _update_txt_record(cfg, base_domain, relative_name, rrset): return _request(cfg, 'PUT', ['domains', base_domain.fqdn, 'records', relative_name, 'TXT'], json={'rrset_values': rrset}) def _update_record(cfg, domain, name, request_runner): base_domain = _get_base_domain(cfg, domain) if base_domain is None: return 'Unable to get base domain for "{}"'.format(domain) relative_name = _get_relative_name(base_domain, name) if relative_name is None: return 'Unable to derive relative name for "{}"'.format(name) response = request_runner(base_domain, relative_name) return None if response.ok else _get_response_message(response) def add_txt_record(cfg, domain, name, value): def requester(base_domain, relative_name): rrset = [value] + _get_txt_record(cfg, base_domain, relative_name) return _update_txt_record(cfg, base_domain, relative_name, rrset) return _update_record(cfg, domain, name, requester) def del_txt_record(cfg, domain, name, value): def requester(base_domain, relative_name): existing = _get_txt_record(cfg, base_domain, relative_name) rrset = list(filter(lambda rr: rr.strip('"') != value, existing)) return _update_txt_record(cfg, base_domain, relative_name, rrset) return _update_record(cfg, domain, name, requester) certbot-plugin-gandi-1.4.3/certbot_plugin_gandi/main.py000066400000000000000000000050771434441153400232310ustar00rootroot00000000000000import logging import uuid from certbot import interfaces, errors from certbot.plugins import dns_common from . import gandi_api logger = logging.getLogger(__name__) def register_authenticator(cls): try: interfaces.Authenticator.register(cls) except AttributeError: import zope.interface zope.interface.implementer(interfaces.IAuthenticator)(cls) zope.interface.provider(interfaces.IPluginFactory)(cls) return cls @register_authenticator class Authenticator(dns_common.DNSAuthenticator): """DNS Authenticator for Gandi (using LiveDNS).""" description = 'Obtain certificates using a DNS TXT record (if you are using Gandi for DNS).' def __init__(self, config, name, **kwargs): super(Authenticator, self).__init__(config, name, **kwargs) self.credentials = None @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ super(Authenticator, cls).add_parser_arguments(add) add('credentials', help='Gandi credentials INI file.') def more_info(self): # pylint: disable=missing-docstring,no-self-use return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ 'the Gandi LiveDNS API.' def _validate_sharing_id(self, credentials): sharing_id = credentials.conf('sharing-id') if sharing_id: try: uuid.UUID(sharing_id, version=4) except ValueError: raise errors.PluginError("Invalid sharing_id: {0}.".format(sharing_id)) def _setup_credentials(self): self.credentials = self._configure_credentials( 'credentials', 'Gandi credentials INI file', { 'api-key': 'API key for Gandi account', }, self._validate_sharing_id ) def _perform(self, domain, validation_name, validation): error = gandi_api.add_txt_record(self._get_gandi_config(), domain, validation_name, validation) if error is not None: raise errors.PluginError('An error occurred adding the DNS TXT record: {0}'.format(error)) def _cleanup(self, domain, validation_name, validation): error = gandi_api.del_txt_record(self._get_gandi_config(), domain, validation_name, validation) if error is not None: logger.warn('Unable to find or delete the DNS TXT record: %s', error) def _get_gandi_config(self): return gandi_api.get_config(api_key = self.credentials.conf('api-key'), sharing_id = self.credentials.conf('sharing-id')) certbot-plugin-gandi-1.4.3/contrib/000077500000000000000000000000001434441153400172005ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/contrib/certbot-dns-gandi-renew.service000066400000000000000000000003601434441153400252030ustar00rootroot00000000000000[Unit] Description=Renew DNS for gandi LiveDNS [Service] Type=oneshot ExecStart=/usr/bin/certbot renew -q --authenticator dns-gandi --dns-gandi-credentials /etc/letsencrypt/gandi.ini --server https://acme-v02.api.letsencrypt.org/directory certbot-plugin-gandi-1.4.3/contrib/certbot-dns-gandi-renew.timer000066400000000000000000000001711434441153400246630ustar00rootroot00000000000000[Unit] Description=Weekly DNS renewal check [Timer] OnCalendar=weekly Persistent=true [Install] WantedBy=timers.target certbot-plugin-gandi-1.4.3/dev/000077500000000000000000000000001434441153400163165ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/dev/README.md000066400000000000000000000006721434441153400176020ustar00rootroot00000000000000# Testing/Development Set env var `GANDI_API_KEY` to your Gandi LiveDNS API key and run `./dev_shell.sh` to get a shell with `certbot` available and the plugin installed. The plugin is installed in "edit" mode, so changes to the source will be effective immediately without any deployment step. # Install development package You can install development package using `pip install 'git+https://github.com/obynio/certbot-plugin-gandi.git'` certbot-plugin-gandi-1.4.3/dev/dev_shell.sh000077500000000000000000000003641434441153400206250ustar00rootroot00000000000000#!/bin/bash set -e SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" PROJ_DIR="$(dirname "$SCRIPT_DIR")" docker run -it --rm --entrypoint /bin/sh -e GANDI_API_KEY -v "$PROJ_DIR:/tmp/work" certbot/certbot:v0.22.0 '/tmp/work/dev/tools/initfile.sh' certbot-plugin-gandi-1.4.3/dev/tools/000077500000000000000000000000001434441153400174565ustar00rootroot00000000000000certbot-plugin-gandi-1.4.3/dev/tools/initfile.sh000066400000000000000000000004421434441153400216150ustar00rootroot00000000000000pip install -e /tmp/work printf 'certbot_plugin_gandi:dns_api_key=%s\n' $GANDI_API_KEY > /tmp/config.ini && chmod 400 /tmp/config.ini echo 'Example: certbot certonly --test-cert -a certbot-plugin-gandi:dns --certbot-plugin-gandi:dns-credentials /tmp/config.ini -d domain.com' exec /bin/sh certbot-plugin-gandi-1.4.3/setup.py000066400000000000000000000032061434441153400172530ustar00rootroot00000000000000from setuptools import setup, find_packages with open("README.md", "r") as fh: long_description = fh.read() setup( name='certbot-plugin-gandi', version='1.4.3', author="Yohann Leon", author_email="yohann@leon.re", description="Certbot plugin for authentication using Gandi LiveDNS", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/obynio/certbot-plugin-gandi", packages=find_packages(), python_requires=' >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', install_requires=[ 'certbot', 'zope.interface', 'requests>=2.4.2', ], entry_points={ 'certbot.plugins': [ 'dns-gandi = certbot_plugin_gandi.main:Authenticator', ], }, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', 'Topic :: System :: Installation/Setup', 'Topic :: System :: Networking', 'Topic :: System :: Systems Administration', 'Topic :: Utilities', ], )