Resolve "Automatic token generation"

This commit is contained in:
sonny 2021-07-18 13:40:03 +00:00
parent a30d4b70ea
commit 57a85158b3
7 changed files with 338 additions and 27 deletions

0
__init__.py Normal file
View file

105
poetry.lock generated
View file

@ -47,6 +47,17 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "cffi"
version = "1.14.6"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "2.0.3" version = "2.0.3"
@ -89,6 +100,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras] [package.extras]
toml = ["toml"] toml = ["toml"]
[[package]]
name = "cryptography"
version = "3.4.7"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.2" version = "3.2"
@ -144,6 +174,14 @@ category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pycparser"
version = "2.20"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "pyflakes" name = "pyflakes"
version = "2.3.1" version = "2.3.1"
@ -272,7 +310,7 @@ sentry = ["sentry_sdk"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "c9ce94c5862b60762859b36d4d9630ffb0d66e8d6f836b447ad088bfcc743629" content-hash = "c7e3ea0e9e6da7d9e725dd2b72bab6a4c6ffae8fe3b28e1de23f3877572e4a22"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
@ -289,6 +327,53 @@ certifi = [
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
] ]
cffi = [
{file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
{file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"},
{file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"},
{file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"},
{file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"},
{file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"},
{file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"},
{file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"},
{file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"},
{file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"},
{file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"},
{file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"},
{file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"},
{file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"},
{file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"},
{file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"},
{file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"},
{file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"},
{file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"},
{file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"},
{file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"},
{file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"},
{file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"},
{file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"},
{file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"},
{file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"},
{file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"},
{file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"},
{file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"},
{file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"},
{file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"},
{file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"},
{file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"},
{file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"},
{file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"},
{file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"},
{file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"},
{file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"},
{file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"},
{file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"},
{file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"},
{file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"},
{file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"},
{file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
{file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
]
charset-normalizer = [ charset-normalizer = [
{file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"}, {file = "charset-normalizer-2.0.3.tar.gz", hash = "sha256:c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12"},
{file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"}, {file = "charset_normalizer-2.0.3-py3-none-any.whl", hash = "sha256:88fce3fa5b1a84fdcb3f603d889f723d1dd89b26059d0123ca435570e848d5e1"},
@ -355,6 +440,20 @@ coverage = [
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
] ]
cryptography = [
{file = "cryptography-3.4.7-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1"},
{file = "cryptography-3.4.7-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6"},
{file = "cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959"},
{file = "cryptography-3.4.7-cp36-abi3-win32.whl", hash = "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d"},
{file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
{file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
{file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
{file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
]
idna = [ idna = [
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
@ -375,6 +474,10 @@ pathspec = [
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
] ]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pyflakes = [ pyflakes = [
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},

View file

@ -11,6 +11,7 @@ click = "^8.0.1"
python-dotenv = "^0.15.0" python-dotenv = "^0.15.0"
requests = "^2.25.1" requests = "^2.25.1"
sentry_sdk = {version = "^0.19.5", optional = true} sentry_sdk = {version = "^0.19.5", optional = true}
cryptography = "^3.4.7"
[tool.poetry.extras] [tool.poetry.extras]
sentry = ["sentry_sdk"] sentry = ["sentry_sdk"]

View file

@ -2,6 +2,7 @@ import click
from transip_client.main import detect from transip_client.main import detect
DEFAULT_DNS = "myip.opendns.com" DEFAULT_DNS = "myip.opendns.com"
DEFAULT_DNS_NAME = "@resolver1.opendns.com" DEFAULT_DNS_NAME = "@resolver1.opendns.com"
DEFAULT_API_URL = "https://api.transip.nl/v6" DEFAULT_API_URL = "https://api.transip.nl/v6"
@ -9,13 +10,39 @@ DEFAULT_API_URL = "https://api.transip.nl/v6"
@click.command() @click.command()
@click.argument("domains", envvar="DOMAINS", nargs=-1) @click.argument("domains", envvar="DOMAINS", nargs=-1)
@click.argument("token", envvar="TOKEN") @click.option("--token", envvar="TOKEN")
@click.option("--login", envvar="LOGIN")
@click.option("--private-key-path", envvar="PRIVATE_KEY_PATH")
@click.option("--dns", envvar="DNS", default=DEFAULT_DNS) @click.option("--dns", envvar="DNS", default=DEFAULT_DNS)
@click.option("--dns-name", envvar="DNS_NAME", default=DEFAULT_DNS_NAME) @click.option("--dns-name", envvar="DNS_NAME", default=DEFAULT_DNS_NAME)
@click.option("--api-url", envvar="API_URL", default=DEFAULT_API_URL) @click.option("--api-url", envvar="API_URL", default=DEFAULT_API_URL)
@click.option("--read-only/--write", envvar="READ_ONLY", default=False) @click.option("--read-only/--write", envvar="READ_ONLY", default=False)
def run(domains, token, dns, dns_name, api_url, read_only): def run(domains, token, login, private_key_path, dns, dns_name, api_url, read_only):
if not domains: if not domains:
raise ValueError("No domain(s) specified") raise ValueError("No domain(s) specified")
detect(domains, (dns, dns_name), api_url, token, read_only) token_retrieval = any(
(
login,
private_key_path,
)
)
if token_retrieval and not all((login, private_key_path)):
raise ValueError(
"Both a login name and the path to a private key need to be specified"
)
elif not token_retrieval and not token:
raise ValueError(
"Either a token or a login name with a path to a private key need"
" to be specified"
)
detect(
domains,
(dns, dns_name),
(private_key_path, login),
token,
api_url,
read_only,
)

View file

@ -1,11 +1,18 @@
import base64
import json import json
import logging import logging
import subprocess import subprocess
import time
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
import requests import requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.hashes import SHA512
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,6 +29,43 @@ def _get_ip(resolvers):
return output.decode("utf-8").strip() return output.decode("utf-8").strip()
def _get_token(private_key_path, login, api_url):
request = requests.Request(
"POST",
f"{api_url}/auth",
json={
"login": login,
"nonce": str(int(time.time() * 1000)),
"read_only": False,
"expiration_time": "30 minutes",
"label": "Custom token",
"global_key": True,
},
)
prepped_request = request.prepare()
with open(private_key_path, "rb") as file:
private_key = serialization.load_pem_private_key(
file.read(), password=None, backend=default_backend()
)
signature = private_key.sign(prepped_request.body, PKCS1v15(), SHA512())
signature = base64.b64encode(signature)
prepped_request.headers["Signature"] = signature.decode("ascii")
with requests.Session() as session:
response = session.send(prepped_request)
if not response.ok:
response.raise_for_status()
response_data = response.json()
return response_data["token"]
def _get_domain(domain, token, api_url): def _get_domain(domain, token, api_url):
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
@ -79,11 +123,15 @@ def _update_domains(updated_domains, api_url, token, read_only):
logger.info(f"Updated domain {domain}") logger.info(f"Updated domain {domain}")
def detect(domains, resolvers, api_url, token, read_only): def detect(domains, resolvers, credentials, token, api_url, read_only):
ip = _get_ip(resolvers) ip = _get_ip(resolvers)
domain_data = _get_domain_data(domains, token, api_url)
updated_domains = {} updated_domains = {}
if all(credentials):
token = _get_token(*credentials, api_url)
domain_data = _get_domain_data(domains, token, api_url)
for data in domain_data: for data in domain_data:
dns_entries = data["dnsEntries"] dns_entries = data["dnsEntries"]
domain = data["domain"] domain = data["domain"]

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAz7ojmkmZ2QA2L0QW/K2BLoxLs6eGWw/LBV5E+beTPz9rejla
Qj/Dq8T7qnBvM8TtLH3Bwwu6H15oqnVis0OhAZTuAFwQuUrzw9g83S+KHxBGATVb
eWQxwkf9ma8GIX+xFnby3XGvVXZjKZgUMZAeFK7c/eXxnRJ8QnN/aoXibiFP0bgS
jjGpsoS6FdjRkau7BbdOJg0fRcTIjkdd11r5pg1oRGAApwU6g+HtjQWYLiNoIxba
8Vv0SSdK9V/hgk/aqLXGmCCjvLqvEK0KxK9y035Sn6C4AnohdiSO5SRYZgipM9F0
RJzc20qCcTL6CpIS4SupA6121eno/bxAQY4q8wIDAQABAoIBAQC3KM9fqWoIFtGw
F3+VSH9RRc8yF5K2FFTU5Ow4q48gA5GG8a8OHx8vA79L51uF8CuYQUJp8psoMZxk
QKDIo+cBeAnrM0Jjvxz1IGN6PAKzpSu0wRFpFdlyDvwjWFo1i1vgDP3UF/ubhYmm
ETws/4AmiJC/JtNFxhjeluxQRsECjLDf52iqO/LbyxQUAElIkMC24ni2SHnLZfQ3
KIBS3MdVULGrAO6dF805vF/CbPajzgsDZzuzcOFjYetHm+qvUV8Tp34M00Mzf5Ar
7joBLCHgeFWfwPIY0l7LnRhs8Z79vAi/slMi/zrQnFi/5aiLE86y195LXeXh59If
BS7eq+YhAoGBAPmGmVR282oMhDFkzR6N2vkKbRCg8p6QMYK3L6/vYYVbf+S4ePH+
GJo41iG76YIx77UPR5wE6+KRStmc6SkvXPy8mJzNVRzMX7xIgtLbNfx37HSqIkKy
CN3rppQ8VTxsaEM8LwKvAQ/V1xxvtmhct08oEQCPqeQHHdrOEvFwl7HbAoGBANUd
56NRDcKmF0mnoCgJpTZxVImpbOcrislQHKJvVjz8JegBcv+JBoX4p/g62FXh0CIJ
fr7KyKsWpH77zniNFu9xgwEs3RaK+z/+GsHsH4IWBkgj4ABweGOiCw+oeN0WdA8w
4okF2VYZbzXbpH3ULrwAvKnElGFcBboSY9yLwHLJAoGBAIGsKz6z2mfAPWqV4esA
+Uz22BsOKUex06kEnemmU13EYUBxhZjs3cg3xUAesYkRfmrvl91CyXsi2m0gmCLp
FD/bmsvSAWtH4nCsliAR/4pGoEE4sTlL4EPD1PuwJvORutVGD4Arhje+f12tyHOP
y0t9nOhXwIhaEm/FLB8AzjSFAoGBAK3NSKZ5KLawi1dnHAbq7tC6lg36nTTd3r6U
1fVmxTbRD/zoiad6UVaa1ilrnBhWI05O3g2tBP/6ZEanBthrf+Pgd81SkC+dQpAK
pDm4Xm3Rlmfo0fqpvpTKhyjK5V6wvA/Tdzv2CCvebELJEJoJm9943mO5TKUlzgnU
i5pGYrl5AoGBAN+b0liV/tDP7NT2pwSTqoW+8itEhBu0QxiHr/QM2941C4+tV4g9
wDC2CkQftSm1zx3unnDvU/WXSeJN4GWI7RmQNdsj84mPZVMfFqhvS8F7czbBMHV8
OHJdQYlyGPHP9WeP1H0K/zuFEcU6sY/Prl0fRIqhTroI6xEaEd+1bZx7
-----END RSA PRIVATE KEY-----

View file

@ -1,7 +1,9 @@
import json import json
import os
from unittest import TestCase from unittest import TestCase
from unittest.mock import call, patch from unittest.mock import call, patch
from pathlib import Path
from click.testing import CliRunner from click.testing import CliRunner
from requests import HTTPError from requests import HTTPError
@ -20,6 +22,9 @@ class RunTestCase(TestCase):
patcher = patch("transip_client.main.requests.put") patcher = patch("transip_client.main.requests.put")
self.mocked_put = patcher.start() self.mocked_put = patcher.start()
patcher = patch("transip_client.main.requests.Session.send")
self.mocked_session = patcher.start()
self.runner = CliRunner() self.runner = CliRunner()
def test_simple(self): def test_simple(self):
@ -46,7 +51,7 @@ class RunTestCase(TestCase):
} }
with self.assertLogs("transip_client.main", level="INFO") as logger: with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke(run, ["foobar.com", "TOKEN"]) result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
self.assertEqual( self.assertEqual(
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"] logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
@ -56,7 +61,7 @@ class RunTestCase(TestCase):
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
expected_json = json.dumps( expected_json = json.dumps(
@ -75,7 +80,7 @@ class RunTestCase(TestCase):
self.mocked_put.assert_called_with( self.mocked_put.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
data=expected_json, data=expected_json,
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
def test_error_response(self): def test_error_response(self):
@ -83,7 +88,7 @@ class RunTestCase(TestCase):
self.mocked_get.return_value.raise_for_status.side_effect = HTTPError self.mocked_get.return_value.raise_for_status.side_effect = HTTPError
with self.assertLogs("transip_client.main", level="INFO") as logger: with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke(run, ["foobar.com", "TOKEN"]) result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
error_log = logger.output[0] error_log = logger.output[0]
@ -92,7 +97,7 @@ class RunTestCase(TestCase):
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
self.mocked_put.assert_not_called() self.mocked_put.assert_not_called()
@ -120,13 +125,13 @@ class RunTestCase(TestCase):
], ],
} }
result = self.runner.invoke(run, ["foobar.com", "TOKEN"]) result = self.runner.invoke(run, ["foobar.com"], env={"TOKEN": "token"})
self.assertEqual(result.exit_code, 0) self.assertEqual(result.exit_code, 0)
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
self.mocked_put.assert_not_called() self.mocked_put.assert_not_called()
@ -154,13 +159,15 @@ class RunTestCase(TestCase):
], ],
} }
result = self.runner.invoke(run, ["foobar.com", "TOKEN", "--read-only"]) result = self.runner.invoke(
run, ["foobar.com", "--read-only"], env={"TOKEN": "token"}
)
self.assertEqual(result.exit_code, 0) self.assertEqual(result.exit_code, 0)
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
self.mocked_put.assert_not_called() self.mocked_put.assert_not_called()
@ -190,7 +197,9 @@ class RunTestCase(TestCase):
with self.assertLogs("transip_client.main", level="INFO") as logger: with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke( result = self.runner.invoke(
run, ["foobar.com", "TOKEN", "--api-url", "https://other-provider.com"] run,
["foobar.com", "--api-url", "https://other-provider.com"],
env={"TOKEN": "token"},
) )
self.assertEqual( self.assertEqual(
@ -201,7 +210,7 @@ class RunTestCase(TestCase):
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
f"https://other-provider.com/domains/foobar.com/dns", f"https://other-provider.com/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
expected_json = json.dumps( expected_json = json.dumps(
@ -220,7 +229,7 @@ class RunTestCase(TestCase):
self.mocked_put.assert_called_with( self.mocked_put.assert_called_with(
f"https://other-provider.com/domains/foobar.com/dns", f"https://other-provider.com/domains/foobar.com/dns",
data=expected_json, data=expected_json,
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
def test_env_var(self): def test_env_var(self):
@ -248,7 +257,12 @@ class RunTestCase(TestCase):
with self.assertLogs("transip_client.main", level="INFO") as logger: with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke( result = self.runner.invoke(
run, ["foobar.com", "TOKEN"], env={"API_URL": "https://new-api.com"} run,
["foobar.com"],
env={
"TOKEN": "token",
"API_URL": "https://new-api.com",
},
) )
self.assertEqual( self.assertEqual(
@ -259,7 +273,7 @@ class RunTestCase(TestCase):
self.mocked_get.assert_called_with( self.mocked_get.assert_called_with(
"https://new-api.com/domains/foobar.com/dns", "https://new-api.com/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
expected_json = json.dumps( expected_json = json.dumps(
@ -278,7 +292,7 @@ class RunTestCase(TestCase):
self.mocked_put.assert_called_with( self.mocked_put.assert_called_with(
"https://new-api.com/domains/foobar.com/dns", "https://new-api.com/domains/foobar.com/dns",
data=expected_json, data=expected_json,
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
) )
def test_multi_arg_env_var(self): def test_multi_arg_env_var(self):
@ -328,7 +342,7 @@ class RunTestCase(TestCase):
with self.assertLogs("transip_client.main", level="INFO") as logger: with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke( result = self.runner.invoke(
run, ["TOKEN"], env={"DOMAINS": "foobar.com foofoo.com"} run, [], env={"TOKEN": "token", "DOMAINS": "foobar.com foofoo.com"}
) )
self.assertIsNone(result.exception) self.assertIsNone(result.exception)
@ -345,13 +359,13 @@ class RunTestCase(TestCase):
expected_calls = [ expected_calls = [
call( call(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
), ),
call().raise_for_status(), call().raise_for_status(),
call().json(), call().json(),
call( call(
f"{DEFAULT_API_URL}/domains/foofoo.com/dns", f"{DEFAULT_API_URL}/domains/foofoo.com/dns",
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
), ),
call().raise_for_status(), call().raise_for_status(),
call().json(), call().json(),
@ -377,13 +391,13 @@ class RunTestCase(TestCase):
call( call(
f"{DEFAULT_API_URL}/domains/foobar.com/dns", f"{DEFAULT_API_URL}/domains/foobar.com/dns",
data=expected_json, data=expected_json,
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
), ),
call().raise_for_status(), call().raise_for_status(),
call( call(
f"{DEFAULT_API_URL}/domains/foofoo.com/dns", f"{DEFAULT_API_URL}/domains/foofoo.com/dns",
data=expected_json, data=expected_json,
headers={"Authorization": "Bearer TOKEN"}, headers={"Authorization": "Bearer token"},
), ),
call().raise_for_status(), call().raise_for_status(),
] ]
@ -391,10 +405,101 @@ class RunTestCase(TestCase):
self.mocked_put.assert_has_calls(expected_calls, any_order=True) self.mocked_put.assert_has_calls(expected_calls, any_order=True)
def test_no_domains(self): def test_no_domains(self):
result = self.runner.invoke(run, ["TOKEN"]) result = self.runner.invoke(run, [], env={"TOKEN": "token"})
self.assertEqual(result.exit_code, 1) self.assertEqual(result.exit_code, 1)
self.assertEqual(str(result.exception), "No domain(s) specified") self.assertEqual(str(result.exception), "No domain(s) specified")
self.mocked_get.assert_not_called() self.mocked_get.assert_not_called()
self.mocked_put.assert_not_called() self.mocked_put.assert_not_called()
def test_no_token_or_login_with_private_key_path(self):
result = self.runner.invoke(run, ["foobar.com"])
self.assertEqual(result.exit_code, 1)
self.assertEqual(
str(result.exception),
"Either a token or a login name with a path to a private key need"
" to be specified"
)
self.mocked_get.assert_not_called()
self.mocked_put.assert_not_called()
def test_login_without_private_key_path(self):
result = self.runner.invoke(run, ["foobar.com"], env={"LOGIN": "foo"})
self.assertEqual(result.exit_code, 1)
self.assertEqual(
str(result.exception),
"Both a login name and the path to a private key need to be specified"
)
self.mocked_get.assert_not_called()
self.mocked_put.assert_not_called()
def test_login_with_private_key_path(self):
self.mocked_dns.return_value = b"111.420\n"
self.mocked_get.return_value.json.return_value = {
"dnsEntries": [
{
"name": "@",
"expire": 60,
"type": "A",
"content": "111.421",
}
],
"_links": [
{
"rel": "self",
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
},
{
"rel": "domain",
"link": "https://api.transip.nl/v6/domains/foobar.com",
},
],
}
self.mocked_session.return_value.json.return_value = {"token": "FOOBAR"}
private_key_path = (
Path(os.path.dirname(__file__)) / "files" / "test-private-key.pem"
)
with self.assertLogs("transip_client.main", level="INFO") as logger:
result = self.runner.invoke(
run,
["foobar.com"],
env={"LOGIN": "foo", "PRIVATE_KEY_PATH": str(private_key_path)}
)
self.assertEqual(
logger.output, ["INFO:transip_client.main:Updated domain foobar.com"]
)
self.assertEqual(result.exit_code, 0)
self.mocked_get.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
headers={"Authorization": "Bearer FOOBAR"},
)
expected_json = json.dumps(
{
"dnsEntries": [
{
"name": "@",
"expire": 60,
"type": "A",
"content": "111.420",
}
]
}
)
self.mocked_put.assert_called_with(
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
data=expected_json,
headers={"Authorization": "Bearer FOOBAR"},
)