mirror of
https://github.com/thegeeklab/certbot-dns-corenetworks.git
synced 2024-11-23 23:20:40 +00:00
initial commit
This commit is contained in:
commit
a5a7cb9f9b
212
.drone.jsonnet
Normal file
212
.drone.jsonnet
Normal file
@ -0,0 +1,212 @@
|
||||
local PythonVersion(pyversion='2.7') = {
|
||||
name: 'python' + std.strReplace(pyversion, '.', ''),
|
||||
image: 'python:' + pyversion,
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
},
|
||||
commands: [
|
||||
'pip install -r dev-requirements.txt -qq',
|
||||
'pip install -qq .',
|
||||
'pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail',
|
||||
],
|
||||
depends_on: [
|
||||
'clone',
|
||||
],
|
||||
};
|
||||
|
||||
local PipelineLint = {
|
||||
kind: 'pipeline',
|
||||
name: 'lint',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'flake8',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
},
|
||||
commands: [
|
||||
'pip install -r dev-requirements.txt -qq',
|
||||
'pip install -qq .',
|
||||
'flake8 ./certbot_dns_corenetworks',
|
||||
],
|
||||
},
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineTest = {
|
||||
kind: 'pipeline',
|
||||
name: 'test',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
PythonVersion(pyversion='2.7'),
|
||||
PythonVersion(pyversion='3.5'),
|
||||
PythonVersion(pyversion='3.6'),
|
||||
PythonVersion(pyversion='3.7'),
|
||||
PythonVersion(pyversion='3.8'),
|
||||
{
|
||||
name: 'codecov',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
CODECOV_TOKEN: { from_secret: 'codecov_token' },
|
||||
},
|
||||
commands: [
|
||||
'pip install codecov -qq',
|
||||
'codecov --required -X gcov',
|
||||
],
|
||||
depends_on: [
|
||||
'python27',
|
||||
'python35',
|
||||
'python36',
|
||||
'python37',
|
||||
'python38',
|
||||
],
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'lint',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineSecurity = {
|
||||
kind: 'pipeline',
|
||||
name: 'security',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'bandit',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
PY_COLORS: 1,
|
||||
},
|
||||
commands: [
|
||||
'pip install -r dev-requirements.txt -qq',
|
||||
'pip install -qq .',
|
||||
'bandit -r ./certbot_dns_corenetworks -x ./certbot_dns_corenetworks/test',
|
||||
],
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'test',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineBuildPackage = {
|
||||
kind: 'pipeline',
|
||||
name: 'build-package',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'build',
|
||||
image: 'python:3.8',
|
||||
environment: {
|
||||
SETUPTOOLS_SCM_PRETEND_VERSION: '${DRONE_TAG##v}',
|
||||
},
|
||||
commands: [
|
||||
'python setup.py sdist bdist_wheel',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'checksum',
|
||||
image: 'alpine',
|
||||
commands: [
|
||||
'cd dist/ && sha256sum * > ../sha256sum.txt',
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'publish-github',
|
||||
image: 'plugins/github-release',
|
||||
settings: {
|
||||
overwrite: true,
|
||||
api_key: { from_secret: 'github_token' },
|
||||
files: ['dist/*', 'sha256sum.txt'],
|
||||
title: '${DRONE_TAG}',
|
||||
note: 'CHANGELOG.md',
|
||||
},
|
||||
when: {
|
||||
ref: ['refs/tags/**'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'publish-pypi',
|
||||
image: 'plugins/pypi',
|
||||
settings: {
|
||||
username: { from_secret: 'pypi_username' },
|
||||
password: { from_secret: 'pypi_password' },
|
||||
repository: 'https://upload.pypi.org/legacy/',
|
||||
skip_build: true,
|
||||
},
|
||||
when: {
|
||||
ref: ['refs/tags/**'],
|
||||
},
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'security',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
|
||||
},
|
||||
};
|
||||
|
||||
local PipelineNotifications = {
|
||||
kind: 'pipeline',
|
||||
name: 'notifications',
|
||||
platform: {
|
||||
os: 'linux',
|
||||
arch: 'amd64',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
name: 'matrix',
|
||||
image: 'plugins/matrix',
|
||||
settings: {
|
||||
homeserver: { from_secret: 'matrix_homeserver' },
|
||||
roomid: { from_secret: 'matrix_roomid' },
|
||||
template: 'Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}',
|
||||
username: { from_secret: 'matrix_username' },
|
||||
password: { from_secret: 'matrix_password' },
|
||||
},
|
||||
when: {
|
||||
status: ['success', 'failure'],
|
||||
},
|
||||
},
|
||||
],
|
||||
depends_on: [
|
||||
'build-package',
|
||||
],
|
||||
trigger: {
|
||||
ref: ['refs/heads/master', 'refs/tags/**'],
|
||||
status: ['success', 'failure'],
|
||||
},
|
||||
};
|
||||
|
||||
[
|
||||
PipelineLint,
|
||||
PipelineTest,
|
||||
PipelineSecurity,
|
||||
PipelineBuildPackage,
|
||||
PipelineNotifications,
|
||||
]
|
240
.drone.yml
Normal file
240
.drone.yml
Normal file
@ -0,0 +1,240 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: lint
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: flake8
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- flake8 ./certbot_dns_corenetworks
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: test
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: python27
|
||||
image: python:2.7
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python35
|
||||
image: python:3.5
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python36
|
||||
image: python:3.6
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python37
|
||||
image: python:3.7
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: python38
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- pytest tests --cov=certbot_dns_corenetworks --no-cov-on-fail
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- clone
|
||||
|
||||
- name: codecov
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install codecov -qq
|
||||
- codecov --required -X gcov
|
||||
environment:
|
||||
CODECOV_TOKEN:
|
||||
from_secret: codecov_token
|
||||
PY_COLORS: 1
|
||||
depends_on:
|
||||
- python27
|
||||
- python35
|
||||
- python36
|
||||
- python37
|
||||
- python38
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
depends_on:
|
||||
- lint
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: security
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: bandit
|
||||
image: python:3.8
|
||||
commands:
|
||||
- pip install -r dev-requirements.txt -qq
|
||||
- pip install -qq .
|
||||
- bandit -r ./certbot_dns_corenetworks -x ./certbot_dns_corenetworks/test
|
||||
environment:
|
||||
PY_COLORS: 1
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
depends_on:
|
||||
- test
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-package
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: python:3.8
|
||||
commands:
|
||||
- python setup.py sdist bdist_wheel
|
||||
environment:
|
||||
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
|
||||
|
||||
- name: checksum
|
||||
image: alpine
|
||||
commands:
|
||||
- cd dist/ && sha256sum * > ../sha256sum.txt
|
||||
|
||||
- name: publish-github
|
||||
image: plugins/github-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: github_token
|
||||
files:
|
||||
- dist/*
|
||||
- sha256sum.txt
|
||||
note: CHANGELOG.md
|
||||
overwrite: true
|
||||
title: ${DRONE_TAG}
|
||||
when:
|
||||
ref:
|
||||
- refs/tags/**
|
||||
|
||||
- name: publish-pypi
|
||||
image: plugins/pypi
|
||||
settings:
|
||||
password:
|
||||
from_secret: pypi_password
|
||||
repository: https://upload.pypi.org/legacy/
|
||||
skip_build: true
|
||||
username:
|
||||
from_secret: pypi_username
|
||||
when:
|
||||
ref:
|
||||
- refs/tags/**
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
- refs/pull/**
|
||||
|
||||
depends_on:
|
||||
- security
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: notifications
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: matrix
|
||||
image: plugins/matrix
|
||||
settings:
|
||||
homeserver:
|
||||
from_secret: matrix_homeserver
|
||||
password:
|
||||
from_secret: matrix_password
|
||||
roomid:
|
||||
from_secret: matrix_roomid
|
||||
template: "Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}"
|
||||
username:
|
||||
from_secret: matrix_username
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- refs/tags/**
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
|
||||
depends_on:
|
||||
- build-package
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 09f1f5c2f46dd90b159b5c5de705afce849ea7fb4772b96aa9827a6d6bb97e3e
|
||||
|
||||
...
|
19
.flake8
Normal file
19
.flake8
Normal file
@ -0,0 +1,19 @@
|
||||
[flake8]
|
||||
ignore = D103, D107, W503
|
||||
max-line-length = 99
|
||||
inline-quotes = double
|
||||
exclude =
|
||||
.git
|
||||
.tox
|
||||
__pycache__
|
||||
build
|
||||
dist
|
||||
test
|
||||
tests
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.cache
|
||||
.eggs
|
||||
env*
|
||||
application-import-names = corenetworks
|
||||
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s
|
60
.github/settings.yml
vendored
Normal file
60
.github/settings.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
repository:
|
||||
name: corenetworks
|
||||
description: Python library for the core-networks.de DNS API
|
||||
homepage: https://corenetworks.geekdocs.de
|
||||
topics: corenetworks, api, dns, python
|
||||
|
||||
private: false
|
||||
has_issues: true
|
||||
has_projects: false
|
||||
has_wiki: false
|
||||
has_downloads: false
|
||||
|
||||
default_branch: master
|
||||
|
||||
allow_squash_merge: true
|
||||
allow_merge_commit: true
|
||||
allow_rebase_merge: true
|
||||
|
||||
labels:
|
||||
- name: bug
|
||||
color: d73a4a
|
||||
description: Something isn't working
|
||||
- name: documentation
|
||||
color: 0075ca
|
||||
description: Improvements or additions to documentation
|
||||
- name: duplicate
|
||||
color: cfd3d7
|
||||
description: This issue or pull request already exists
|
||||
- name: enhancement
|
||||
color: a2eeef
|
||||
description: New feature or request
|
||||
- name: good first issue
|
||||
color: 7057ff
|
||||
description: Good for newcomers
|
||||
- name: help wanted
|
||||
color: 008672
|
||||
description: Extra attention is needed
|
||||
- name: invalid
|
||||
color: e4e669
|
||||
description: This doesn't seem right
|
||||
- name: question
|
||||
color: d876e3
|
||||
description: Further information is requested
|
||||
- name: wontfix
|
||||
color: ffffff
|
||||
description: This will not be worked on
|
||||
|
||||
branches:
|
||||
- name: master
|
||||
protection:
|
||||
required_pull_request_reviews: null
|
||||
required_status_checks:
|
||||
strict: true
|
||||
contexts:
|
||||
- continuous-integration/drone/pr
|
||||
enforce_admins: null
|
||||
restrictions: null
|
||||
|
||||
...
|
113
.gitignore
vendored
Normal file
113
.gitignore
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env*
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
env*/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Ignore ide addons
|
||||
.server-script
|
||||
.on-save.json
|
||||
.vscode
|
||||
.pytest_cache
|
||||
|
||||
pip-wheel-metadata
|
||||
|
||||
# Hugo documentation
|
||||
docs/themes/
|
||||
docs/public/
|
||||
resources/_gen/
|
||||
|
||||
# Misc
|
||||
.local/
|
||||
.corenetworks*
|
||||
docs/content/api/corenetworks/
|
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
@ -0,0 +1 @@
|
||||
* initial release
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Robert Kaussow <mail@geeklabor.de>
|
||||
|
||||
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 (including the next
|
||||
paragraph) 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.
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# certbot-dns-corenetworks
|
||||
|
||||
[![Build Status](https://img.shields.io/drone/build/xoxys/certbot-dns-corenetworks?logo=drone)](https://cloud.drone.io/xoxys/certbot-dns-corenetworks)
|
||||
[![Python Version](https://img.shields.io/pypi/pyversions/certbot-dns-corenetworks.svg)](https://pypi.org/project/certbot-dns-corenetworks/)
|
||||
[![PyPi Status](https://img.shields.io/pypi/status/certbot-dns-corenetworks.svg)](https://pypi.org/project/certbot-dns-corenetworks/)
|
||||
[![PyPi Release](https://img.shields.io/pypi/v/certbot-dns-corenetworks.svg)](https://pypi.org/project/certbot-dns-corenetworks/)
|
||||
[![License: MIT](https://img.shields.io/github/license/xoxys/certbot-dns-corenetworks)](LICENSE)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Maintainers and Contributors
|
||||
|
||||
[Robert Kaussow](https://github.com/xoxys)
|
10
certbot_dns_corenetworks/__init__.py
Normal file
10
certbot_dns_corenetworks/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Default package."""
|
||||
|
||||
__author__ = "Robert Kaussow"
|
||||
__project__ = "certbot_dns_corenetworks"
|
||||
__license__ = "MIT"
|
||||
__maintainer__ = "Robert Kaussow"
|
||||
__email__ = "mail@geeklabor.de"
|
||||
__url__ = "https://github.com/xoxys/certbot-dns-corenetworks"
|
||||
__version__ = "0.1.0"
|
228
certbot_dns_corenetworks/dns_corenetworks.py
Normal file
228
certbot_dns_corenetworks/dns_corenetworks.py
Normal file
@ -0,0 +1,228 @@
|
||||
"""DNS Authenticator for Core Networks."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
import zope.interface # noqa
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot.plugins import dns_common
|
||||
from corenetworks import CoreNetworks
|
||||
from corenetworks.exceptions import AuthError
|
||||
from corenetworks.exceptions import CoreNetworksException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(dns_common.DNSAuthenticator):
|
||||
"""DNS Authenticator for Core Networks DNS API."""
|
||||
|
||||
description = (
|
||||
"Obtain certificates using a DNS TXT record "
|
||||
"(if you are using Core Networks for your domains)."
|
||||
)
|
||||
ttl = 300
|
||||
|
||||
clientCache = {} # noqa
|
||||
nameCache = {} # noqa
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize an Core Networks Authenticator."""
|
||||
super(Authenticator, self).__init__(*args, **kwargs)
|
||||
self.credentials = None
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add): # noqa
|
||||
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60)
|
||||
add(
|
||||
"credentials",
|
||||
help=("Path to Core Networks account credentials INI file"),
|
||||
default="/etc/letsencrypt/corenetworks.cfg"
|
||||
)
|
||||
|
||||
def more_info(self): # noqa
|
||||
return "This plugin configures a DNS TXT record to respond to a dns-01 challenge using " \
|
||||
"the Core Networks DNS API."
|
||||
|
||||
def _setup_credentials(self):
|
||||
self.credentials = self._configure_credentials(
|
||||
"credentials", "path to Core Networks API credentials INI file", {
|
||||
"username": "Username of the Core Networks API account.",
|
||||
"password": "Password of the Core Networks API account.",
|
||||
}
|
||||
)
|
||||
|
||||
def _follow_cnames(self, domain, validation_name):
|
||||
"""
|
||||
Perform recursive CNAME lookups.
|
||||
|
||||
In case there exist a CNAME for the given validation name a recursive CNAME lookup
|
||||
will be performed automatically. If the optional dependency dnspython is not installed,
|
||||
the given name is simply returned.
|
||||
"""
|
||||
try:
|
||||
import dns.exception # noqa
|
||||
import dns.resolver # noqa
|
||||
import dns.name # noqa
|
||||
except ImportError:
|
||||
return validation_name
|
||||
|
||||
resolver = dns.resolver.Resolver()
|
||||
name = dns.name.from_text(validation_name)
|
||||
while 1:
|
||||
try:
|
||||
answer = resolver.query(name, "CNAME")
|
||||
if 1 <= len(answer):
|
||||
name = answer[0].target
|
||||
else:
|
||||
break
|
||||
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
|
||||
break
|
||||
except (dns.exception.Timeout, dns.resolver.YXDOMAIN, dns.resolver.NoNameservers):
|
||||
raise errors.PluginError(
|
||||
"Failed to lookup CNAMEs on your requested domain {0}".format(domain)
|
||||
)
|
||||
return name.to_text(True)
|
||||
|
||||
def _perform(self, domain, validation_name, validation):
|
||||
if validation_name in Authenticator.nameCache:
|
||||
resolved = Authenticator.nameCache[validation_name]
|
||||
else:
|
||||
resolved = self._follow_cnames(domain, validation_name)
|
||||
Authenticator.nameCache[validation_name] = resolved
|
||||
|
||||
if resolved != validation_name:
|
||||
logger.info("Validation record for %s redirected by CNAME(s) to %s", domain, resolved)
|
||||
|
||||
self._get_corenetworks_client().add_txt_record(domain, resolved, validation, self.ttl)
|
||||
|
||||
def _cleanup(self, domain, validation_name, validation):
|
||||
resolved = Authenticator.nameCache[validation_name]
|
||||
self._get_corenetworks_client().del_txt_record(domain, resolved, validation)
|
||||
|
||||
def _get_corenetworks_client(self):
|
||||
key = self.conf("credentials")
|
||||
|
||||
if key in Authenticator.clientCache:
|
||||
client = Authenticator.clientCache[key]
|
||||
else:
|
||||
client = _CorenetworksClient(
|
||||
self.credentials.conf("username"), self.credentials.conf("password")
|
||||
)
|
||||
Authenticator.clientCache[key] = client
|
||||
|
||||
return client
|
||||
|
||||
|
||||
class _CorenetworksClient(object):
|
||||
"""Encapsulates all communication with the Core Networks API."""
|
||||
|
||||
def __init__(self, user, password, auto_commit=True):
|
||||
try:
|
||||
self.client = CoreNetworks(user, password, auto_commit=auto_commit)
|
||||
except AuthError as e:
|
||||
raise errors.PluginError("Login failed: {0}".format(str(e)))
|
||||
|
||||
def add_txt_record(self, domain_name, record_name, record_content, record_ttl):
|
||||
"""
|
||||
Add a TXT record using the supplied information.
|
||||
|
||||
Args:
|
||||
domain_name (str): The requested domain for validation.
|
||||
record_name (str): The record name (typically beginning with "_acme-challenge.").
|
||||
record_content (str): The record content (typically the challenge validation).
|
||||
record_ttl(int): The record TTL (number of seconds that the record may be cached).
|
||||
|
||||
Raises:
|
||||
certbot.errors.PluginError: If an error occurs communicating with the DNS server
|
||||
|
||||
"""
|
||||
zone = self._find_zone(record_name)
|
||||
name = re.sub(r"\.{}$".format(zone), "", record_name)
|
||||
|
||||
try:
|
||||
self.client.add_record(
|
||||
zone, {
|
||||
"name": name,
|
||||
"type": "TXT",
|
||||
"data": record_content,
|
||||
"ttl": record_ttl
|
||||
}
|
||||
)
|
||||
except CoreNetworksException:
|
||||
raise errors.PluginError(
|
||||
"Failed to add TXT DNS record {record} to {zone} for {domain}".format(
|
||||
record=record_name, zone=zone, domain=domain_name
|
||||
)
|
||||
)
|
||||
|
||||
def del_txt_record(self, domain_name, record_name, record_content):
|
||||
"""
|
||||
Delete a TXT record using the supplied information.
|
||||
|
||||
Args:
|
||||
domain_name (str): The requested domain for validation.
|
||||
record_name (str): The record name (typically beginning with "_acme-challenge.").
|
||||
record_content (str): The record content (typically the challenge validation).
|
||||
|
||||
Returns:
|
||||
certbot.errors.PluginError: if an error occurs communicating with the DNS server
|
||||
|
||||
"""
|
||||
zone = self._find_zone(record_name)
|
||||
name = re.sub(r"\.{}$".format(zone), "", record_name)
|
||||
|
||||
try:
|
||||
info = self.client.records(zone, {"name": name, "type": "TXT", "data": record_content})
|
||||
if (len(info) != 1 or info[0]["name"] != name):
|
||||
raise NameError("Unknown record")
|
||||
except NameError as e:
|
||||
raise errors.PluginError(
|
||||
"Record {record} not found: {err}".format(record=record_name, err=e)
|
||||
)
|
||||
except CoreNetworksException as e:
|
||||
raise errors.PluginError(
|
||||
"Could not lookup record {record}: {err}".format(record=record_name, err=e)
|
||||
)
|
||||
|
||||
try:
|
||||
self.client.delete_record(zone, {"name": name, "type": "TXT", "data": record_content})
|
||||
except CoreNetworksException:
|
||||
raise errors.PluginError(
|
||||
"Failed to delete TXT DNS record {record} of {zone} for {domain}".format(
|
||||
record=record_name, zone=zone, domain=domain_name
|
||||
)
|
||||
)
|
||||
|
||||
def _find_zone(self, domain_name):
|
||||
"""
|
||||
Find the base domain name for a given domain name.
|
||||
|
||||
:param str domain_name: The domain name for which to find the corresponding base domain.
|
||||
:returns: The base domain name, if found.
|
||||
:rtype: str
|
||||
:raises certbot.errors.PluginError: if no matching domain is found.
|
||||
"""
|
||||
domain_name_guesses = dns_common.base_domain_name_guesses(domain_name)
|
||||
|
||||
for guess in domain_name_guesses:
|
||||
logger.debug("Testing {0} for domain {1}...".format(guess, domain_name))
|
||||
try:
|
||||
info = self.client.zone(guess)[0]
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
logger.debug("Found zone '{zone}': {info}".format(zone=guess, info=info))
|
||||
if not info.get("active"):
|
||||
raise errors.PluginError("Zone {0} is not active".format(guess))
|
||||
if info.get("type") != "master":
|
||||
raise errors.PluginError("Zone {0} is not a master zone".format(guess))
|
||||
|
||||
return guess
|
||||
|
||||
raise errors.PluginError(
|
||||
"Unable to determine base domain for {0} using names: {1}".format(
|
||||
domain_name, domain_name_guesses
|
||||
)
|
||||
)
|
19
dev-requirements.txt
Normal file
19
dev-requirements.txt
Normal file
@ -0,0 +1,19 @@
|
||||
pydocstyle
|
||||
flake8
|
||||
flake8-colors
|
||||
flake8-blind-except
|
||||
flake8-builtins
|
||||
flake8-docstrings
|
||||
flake8-isort
|
||||
flake8-logging-format
|
||||
flake8-polyfill
|
||||
flake8-quotes
|
||||
flake8-pep3101
|
||||
flake8-eradicate; python_version >= "3.6"
|
||||
pep8-naming
|
||||
wheel
|
||||
pytest
|
||||
pytest-mock
|
||||
pytest-cov
|
||||
bandit
|
||||
yapf
|
30
setup.cfg
Normal file
30
setup.cfg
Normal file
@ -0,0 +1,30 @@
|
||||
[metadata]
|
||||
description-file = README.md
|
||||
license_file = LICENSE
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[isort]
|
||||
default_section = THIRDPARTY
|
||||
known_first_party = certbot_dns_corenetworks
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||
force_single_line = true
|
||||
line_length = 99
|
||||
skip_glob = **/.env*,**/env/*,**/docs/*
|
||||
|
||||
[yapf]
|
||||
based_on_style = google
|
||||
column_limit = 99
|
||||
dedent_closing_brackets = true
|
||||
coalesce_brackets = true
|
||||
split_before_logical_operator = true
|
||||
|
||||
[tool:pytest]
|
||||
filterwarnings =
|
||||
ignore::FutureWarning
|
||||
ignore:.*collections.*:DeprecationWarning
|
||||
ignore:.*pep8.*:FutureWarning
|
||||
|
||||
[coverage:run]
|
||||
omit = **/test/*
|
78
setup.py
Normal file
78
setup.py
Normal file
@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Setup script for the package."""
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
|
||||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
PACKAGE_NAME = "certbot_dns_corenetworks"
|
||||
|
||||
|
||||
def get_property(prop, project):
|
||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
result = re.search(
|
||||
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
|
||||
open(os.path.join(current_dir, project, "__init__.py")).read(),
|
||||
)
|
||||
return result.group(1)
|
||||
|
||||
|
||||
def get_readme(filename="README.md"):
|
||||
this = os.path.abspath(os.path.dirname(__file__))
|
||||
with io.open(os.path.join(this, filename), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
return long_description
|
||||
|
||||
|
||||
setup(
|
||||
name=get_property("__project__", PACKAGE_NAME),
|
||||
description="Core Networks DNS Authenticator plugin for Certbot",
|
||||
keywords="dns, certbot, automation, corenetworks",
|
||||
version=get_property("__version__", PACKAGE_NAME),
|
||||
author=get_property("__author__", PACKAGE_NAME),
|
||||
author_email=get_property("__email__", PACKAGE_NAME),
|
||||
url=get_property("__url__", PACKAGE_NAME),
|
||||
license=get_property("__license__", PACKAGE_NAME),
|
||||
long_description=get_readme(),
|
||||
long_description_content_type="text/markdown",
|
||||
packages=find_packages(exclude=["*.test", "test", "test.*"]),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,<4",
|
||||
entry_points={
|
||||
"certbot.plugins": [
|
||||
"dns-corenetworks = certbot_dns_corenetworks.dns_corenetworks:Authenticator"
|
||||
],
|
||||
},
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Plugins",
|
||||
"Intended Audience :: System Administrators",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: POSIX",
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Topic :: Security",
|
||||
"Topic :: System :: Networking",
|
||||
"Topic :: Utilities",
|
||||
],
|
||||
install_requires=[
|
||||
"acme",
|
||||
"certbot>=0.15",
|
||||
"setuptools",
|
||||
"zope.interface",
|
||||
"corenetworks",
|
||||
],
|
||||
dependency_links=[],
|
||||
)
|
84
tests/dns_corenetwork_test.py
Normal file
84
tests/dns_corenetwork_test.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""Tests for certbot_dns_ispconfig.dns_ispconfig."""
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.plugins import dns_test_common
|
||||
from certbot.plugins.dns_test_common import DOMAIN
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
API_USER = "my_user"
|
||||
API_PASSWORD = "secure"
|
||||
|
||||
|
||||
class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest):
|
||||
"""Test for Hetzner DNS Authenticator."""
|
||||
|
||||
def setUp(self):
|
||||
from certbot_dns_corenetworks.dns_corenetworks import Authenticator
|
||||
|
||||
super(AuthenticatorTest, self).setUp()
|
||||
|
||||
path = os.path.join(self.tempdir, "file.ini")
|
||||
dns_test_common.write({
|
||||
"corenetworks_username": API_USER,
|
||||
"corenetworks_password": API_PASSWORD
|
||||
}, path)
|
||||
|
||||
self.config = mock.MagicMock(
|
||||
corenetworks_credentials=path, corenetworks_propagation_seconds=0
|
||||
) # don't wait during tests
|
||||
|
||||
self.auth = Authenticator(self.config, "corenetworks")
|
||||
|
||||
self.mock_client = mock.MagicMock()
|
||||
# _get_corenetworks_client | pylint: disable=protected-access
|
||||
self.auth._get_corenetworks_client = mock.MagicMock(return_value=self.mock_client)
|
||||
|
||||
def test_perform(self):
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
expected = [
|
||||
mock.call.add_txt_record(DOMAIN, "_acme-challenge." + DOMAIN, mock.ANY, mock.ANY)
|
||||
]
|
||||
self.assertEqual(expected, self.mock_client.mock_calls)
|
||||
|
||||
def test_cleanup(self):
|
||||
# _attempt_cleanup | pylint: disable=protected-access
|
||||
self.auth.nameCache["_acme-challenge." + DOMAIN] = "_acme-challenge." + DOMAIN
|
||||
self.auth._attempt_cleanup = True
|
||||
self.auth.cleanup([self.achall])
|
||||
|
||||
expected = [mock.call.del_txt_record(DOMAIN, "_acme-challenge." + DOMAIN, mock.ANY)]
|
||||
self.assertEqual(expected, self.mock_client.mock_calls)
|
||||
|
||||
def test_creds(self):
|
||||
dns_test_common.write({
|
||||
"corenetworks_username": API_USER,
|
||||
"corenetworks_password": API_PASSWORD
|
||||
}, self.config.corenetworks_credentials)
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
expected = [
|
||||
mock.call.add_txt_record(DOMAIN, "_acme-challenge." + DOMAIN, mock.ANY, mock.ANY)
|
||||
]
|
||||
self.assertEqual(expected, self.mock_client.mock_calls)
|
||||
|
||||
def test_no_creds(self):
|
||||
dns_test_common.write({}, self.config.corenetworks_credentials)
|
||||
self.assertRaises(errors.PluginError, self.auth.perform, [self.achall])
|
||||
|
||||
def test_missing_user_or_password(self):
|
||||
dns_test_common.write({"corenetworks_username": API_USER},
|
||||
self.config.corenetworks_credentials)
|
||||
self.assertRaises(errors.PluginError, self.auth.perform, [self.achall])
|
||||
|
||||
dns_test_common.write({"corenetworks_password": API_PASSWORD},
|
||||
self.config.corenetworks_credentials)
|
||||
self.assertRaises(errors.PluginError, self.auth.perform, [self.achall])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user