Giter Club home page Giter Club logo

lexicon's Introduction

Lexicon

Manipulate DNS records on various DNS providers in a standardized/agnostic way.

build_status coverage_status docker_pulls pypy_version pypy_python_support github_license

Table of Contents

Why using Lexicon?

Lexicon provides a way to manipulate DNS records on multiple DNS providers in a standardized way.

Lexicon can be used as:

  • a CLI tool:
# Create a TXT entry in domain.net zone hosted by CloudFlare
lexicon cloudflare create domain.net TXT --name foo --content bar
  • or a Python library:
# Create a TXT entry in domain.net zone hosted by CloudFlare
from lexicon.client import Client
from lexicon.config import ConfigResolver

config = ConfigResolver().with_env().with_dict({
    "provider_name" : "cloudflare",
    "domain": "domain.net",
})

with Client(config) as operations:
    operations.create_record("TXT", "foo", "bar")

Lexicon was designed to be used in automation, specifically letsencrypt.

Supported providers

Only DNS providers who have an API can be supported by lexicon.

The current supported providers are:

aliyun aurora azure cloudflare cloudns
cloudxns conoha constellix ddns digitalocean
dinahosting directadmin dnsimple dnsmadeeasy dnspark
dnspod dnsservices dreamhost duckdns dynu
easydns easyname euserv exoscale flexibleengine
gandi gehirn glesys godaddy googleclouddns
gransy gratisdns henet hetzner hostingde
hover infoblox infomaniak internetbs inwx
joker linode linode4 localzone luadns
memset misaka mythicbeasts namecheap namecom
namesilo netcup nfsn njalla nsone
oci onapp online ovh plesk
pointhq porkbun powerdns rackspace rage4
rcodezero route53 safedns sakuracloud softlayer
transip ultradns valuedomain vercel vultr
webgo wedos yandex yandexcloud zeit
zilore zonomi

Documentation

Online documentation (user guide, configuration reference) is available in the Lexicon documentation.

For a quick start, please have a look in particular at the User guide.

Contributing

If you want to help in the Lexicon development, you are welcome!

Please have a look at the Developer guide page to know how to start.

Licensing

  • MIT
  • Logo: transform by Mike Rowe from the Noun Project

lexicon's People

Contributors

adferrand avatar adherzog avatar ags-slc avatar alexzorin avatar analogj avatar capsulecd avatar chibiegg avatar dependabot-preview[bot] avatar dependabot[bot] avatar hank avatar jarossi avatar joostdebruijn avatar kapouer avatar kaz avatar lordgaav avatar miff2000 avatar mschoettle avatar nealian avatar oldium avatar packagr-io-beta[bot] avatar paskal avatar rbelnap avatar rembik avatar rmarscher avatar skeen avatar tasos-ukko avatar trinopoty avatar whi-tw avatar zh99998 avatar zjs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lexicon's Issues

CloudNS

@AnalogJ, I created a working version for CloudNS (cloudns.net) API, how would you like it contributed?

(I ask because when I looked here, you said you were traveling and you will add a doc specifying how developers can write and test their own providers.)

I have been using my CloudNS version in production for numerous domains without issue.

Please let me know.

Thank you

Environment variables with PowerDNS Provider do not work

I'm trying to use the following environment variables to configure the PowerDNS provider:

LEXICON_POWERDNS_TOKEN=a_valid_token
LEXICON_POWERDNS_PDNS_SERVER=https://a.valid.powerdns.server.com

But running "lexicon powerdns list domain.com TXT" gives:

Arguments: Namespace(action='list', auth_token=None, content=None, delegated=None, domain='domain.com', identifier=None, name=None, pdns_server=None, pdns_server_id=None, priority=None, provider_name='powerdns', ttl=None, type='TXT')
Traceback (most recent call last):
  File "/usr/bin/lexicon", line 11, in <module>
    load_entry_point('dns-lexicon==2.1.10', 'console_scripts', 'lexicon')()
  File "/usr/lib/python3.5/site-packages/lexicon/__main__.py", line 68, in main
    client = Client(parsed_args.__dict__)
  File "/usr/lib/python3.5/site-packages/lexicon/client.py", line 34, in __init__
    self.provider = provider_class(self.options)
  File "/usr/lib/python3.5/site-packages/lexicon/providers/powerdns.py", line 49, in __init__
    if self.api_endpoint.endswith('/'):
AttributeError: 'NoneType' object has no attribute 'endswith'

Running with the switch works fine:

lexicon powerdns list domain.com TXT --auth-token a_valid_token --pdns-server https://a.valid.powerdns.server.com

Am I doing anything wrong?

Gandi provider in README but not in project

I am using Gandi, and wanted to use lexicon to speak with it.
Unfortunately the provider is not available publicly. I have found it in the issues... but doesn't work correctly.
I would be nice to correct it and add it to the project!
Thank you in advance for your work :-)

Add support for HostEurope

It's no pure DNS provider, but a big provider for webhosting worldwide. What are the chances to add HostEurope support?

Route53: Public and Private Zones can't be distinguished.

I've been testing out lexicon for updating DNS records via Route53, and I have a Public and Private Zone with the same domain name.
I noticed that lexicon is only searching for the domain name by name, so in my case, my internal zone was the first created and so it's the only thing lexicon itself finds for my domain name.

I was going to have it update a record for my home IP address for dynamic IP issues, but what's happening is it is only updating the Private zone's record. I've specified --identifier with the ZoneID of the Public Zone, but that is not working either.

I didn't even have a record for home.mydomain.com in my Private Zone, and it ended up creating the record just to fullfill the update. I do see in the output of lexicon both private and public zones, including the true|false specifically identifying it as private or not.

I'd like to be able to update both, differently as needed.

Suggestion to add provider support for easyDNS

I would like to suggest adding support for easyDNS as a provider to lexicon. Documentation is available at http://docs.sandbox.rest.easydns.net/, and the signup for an API key is located at http://docs.sandbox.rest.easydns.net/beta_signup.php

I am not fluent in Python (hence the lack of a pull request), but I am very willing to help test code using my own easyDNS account / domains. I'm particularly interested in this for use with Let's Encrypt's DNS challenges.

Thanks for considering this!

unable to install transip dependencies or use transip plugin

Output: pip install dns-lexicon[transip]

Requirement already satisfied: dns-lexicon[transip] in ./lib/python2.7/site-packages
Requirement already satisfied: requests in ./lib/python2.7/site-packages (from dns-lexicon[transip])
Requirement already satisfied: future in ./lib/python2.7/site-packages (from dns-lexicon[transip])
Requirement already satisfied: tldextract in ./lib/python2.7/site-packages (from dns-lexicon[transip])
Collecting transip==0.1.0-dev; extra == "transip" (from dns-lexicon[transip])
  Could not find a version that satisfies the requirement transip==0.1.0-dev; extra == "transip" (from dns-lexicon[transip]) (from versions: 0.2)
No matching distribution found for transip==0.1.0-dev; extra == "transip" (from dns-lexicon[transip])

after manual installing the transip package i get the following error

Namespace(action='list', auth_api_key='../test-acme/private', auth_username='foobar', content='foo', delegated=None, domain='example.org', identifier=None, name='foo', priority=None, provider_name='transip', ttl=None, type='NS')
Traceback (most recent call last):
  File "./bin/lexicon", line 11, in <module>
    sys.exit(main())
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/lexicon/__main__.py", line 56, in main
    client.execute()
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/lexicon/client.py", line 36, in execute
    self.provider.authenticate()
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/lexicon/providers/transip.py", line 43, in authenticate
    self.client.get_info(domain)
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/transip/service/domain.py", line 26, in get_info
    cookie = self.build_cookie(mode=MODE_RO, method='getInfo', parameters=[domain_name])
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/transip/client.py", line 111, in build_cookie
    timestamp=timestamp, nonce=nonce, additional=parameters))
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/transip/client.py", line 51, in _sign
    privkey = rsa.PrivateKey.load_pkcs1(keydata)
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/rsa/key.py", line 75, in load_pkcs1
    return method(keyfile)
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/rsa/key.py", line 511, in _load_pkcs1_pem
    return cls._load_pkcs1_der(der)
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/rsa/key.py", line 459, in _load_pkcs1_der
    as_ints = tuple(int(x) for x in priv[1:9])
  File "/home/muller/lexicon/local/lib/python2.7/site-packages/rsa/key.py", line 459, in <genexpr>
    as_ints = tuple(int(x) for x in priv[1:9])
TypeError: int() argument must be a string or a number, not 'Sequence'

ImportError: No module named transip.client

Calling lexicon throws:

Traceback (most recent call last):
  File "/Users/tom/Library/Python/2.7/bin/lexicon", line 9, in <module>
    load_entry_point('dns-lexicon==1.1.15', 'console_scripts', 'lexicon')()
  File "/Users/tom/Library/Python/2.7/lib/python/site-packages/lexicon/__main__.py", line 49, in main
    parsed_args = MainParser().parse_args()
  File "/Users/tom/Library/Python/2.7/lib/python/site-packages/lexicon/__main__.py", line 39, in MainParser
    provider_module = importlib.import_module('lexicon.providers.' + provider)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/importlib/__init__.py", line 37, in import_module
    __import__(name)
  File "/Users/tom/Library/Python/2.7/lib/python/site-packages/lexicon/providers/transip.py", line 3, in <module>
    from transip.client import DomainClient
ImportError: No module named transip.client

dns-lexicon (1.1.15)

dnsmadeeasy help

i want use acme.sh with dnsmadeeasy but how insert api key and use it for auth i use like example of cloudflare but not work?

unable to create records - "create_record: False"

I've installed lexicon with pip in a virtual env with the "stankevich/python" puppet module, I only mention this to verify the installation is consistent between hosts.

I'm using the same provider (dnsmadeeasy) and environment variables (LEXICON_DNSMADEEASY_USERNAME, LEXICON_DNSMADEEASY_TOKEN) on both hosts.

When I run try to add records from one host, I'm getting "create_record: False" and when i check the dnsmadeasy web frontend, the record I try to create does not exist.

On the other host, the command returns "create_record: True" and it works as expected.

I'm curious if there are any steps I can take to debug this further.

luadns provider script breaks with default ttl

When leaving ttl unspecified, the script ends up calling the luadns API with the ttl parameter set as 'None' instead of the default. This results in the call failing. I'm creating a pull request with a fix for this.

TTLs hard-coded and not even passed to providers from the command line

I just started to use lexicon with acme.sh and after banging my head to the wall for a while (and even suspecting that DNS Made Easy's API was not working properly as my records kept being generated with TTL of 84600 no matter what I did) before I realized to check the sources here - and, lo and behold, for example here..

..the value of TTL really is hard-coded to 84600 (value varies in other providers).

(Not that I should need to explain my frustration, but that isn't very good for example for TXT records that are only very infrequently queried (so short TTL does not matter) but in the case of errors really do need to be able to be fixed quicker than that as processing the certificates via ACME protocol is highly automated and things can go wrong sometimes - mostly because of user errors.)

Also, the value from --ttl is never even passed to the providers (that also are not prepared to receive it) when creating or updating the record, as seen here:

return self.provider.create_record(self.options.get('type'), self.options.get('name'), self.options.get('content'))

..and here:

def create_record(self, type, name, content):

This seems like a pretty elementary bug to me, maybe somebody lost their train of thought while implementing this parameter? :)

DnsMadeEasy authentication issue

Hey everyone,

Dnsmadeeasy is throwing an exception with the following command:
lexicon dnsmadeeasy --auth-username="xxxx" --auth-token="xxxx" list mydomain.com TXT where auth-username is API key & auth-token is Secret key.

root@test:~# lexicon dnsmadeeasy --auth-username="xxxx" --auth-token="xxxx" list mydomain.com TXT
Arguments: Namespace(action='list', auth_token='xxxx', auth_username='xxxx', content=None, delegated=None, domain='mydomain.com', identifier=None, name=None, priority=None, provider_name='dnsmadeeasy', ttl=None, type='TXT')
Starting new HTTPS connection (1): api.dnsmadeeasy.com
https://api.dnsmadeeasy.com:443 "GET /V2.0/dns/managed/name?domainname=mydomain.com HTTP/1.1" 403 79
Traceback (most recent call last):
  File "/usr/local/bin/lexicon", line 11, in <module>
    load_entry_point('dns-lexicon==2.1.12', 'console_scripts', 'lexicon')()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/__main__.py", line 69, in main
    client.execute()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/client.py", line 38, in execute
    self.provider.authenticate()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/dnsmadeeasy.py", line 40, in authenticate
    raise e
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://api.dnsmadeeasy.com/V2.0/dns/managed/name?domainname=mydomain.com

I've already made sure the keys are correct. Appreciate any tips on troubleshooting this.

EDIT: This issue has been resolved. If anyone is still facing issues relating to authentication, reset your clock with correct date/time.

Kind regards,
Pavin Joseph.

error: argument --content: expected one argument

I was getting this error occasionally while creating zone entries for letsencrypt challenges. Managed to track it down to a content string beginning with a hyphen. The argument parsing library lexicon uses seems to misinterpret that as another argument rather than the value of the content argument.

Worked around it by replacing --content "$challenge" with --content="$challenge". Not sure how easy it is to modify the usage output of argsparse, but it might be an idea to recommend this style in the acme-challenge readme examples.

Fix logging TypeError (digitalocean.py)

This same issue exists in lexicon/providers/digitalocean.py line 111. The same edit is needed to fix it.

The error generated is:

Traceback (most recent call last):
  File "/usr/lib/python2.7/logging/__init__.py", line 861, in emit
    msg = self.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 734, in format
    return fmt.format(record)
  File "/usr/lib/python2.7/logging/__init__.py", line 465, in format
    record.message = record.getMessage()
  File "/usr/lib/python2.7/logging/__init__.py", line 329, in getMessage
    msg = msg % self.args
TypeError: not all arguments converted during string formatting
Logged from file digitalocean.py, line 111

That section is:

 Line 110: # is always True at this point, if a non 200 response is returned an error is raised.
 Line 111: logger.debug('delete_record: {0}', True)
 Line 112: return True

trying to develop a new provider

I'm a total newb at Python... but I really need that GoDaddy provider, so I thought I'd start trying to bang something together.

After following the instructions in CONTRIBUTING, I found that none of the changes I was making to source seemed to be taking effect. I'm not sure I've actually found the issue (because I'm a newb with Python, and I find this all very confusing!)... but after review, I noticed that running pip install -r optional-requirements.txt seems to also run python setup.py install as part of the process.

Does this take effect over the later command of python setup.py develop, or does the later command overwrite the effects of the previous install?

How do I upgrade Lexicon?

This may be a stupid question, but I haven't found it addressed anywhere. I've just checked and my current version is 2.10 from when I installed in July. What is the proper way to upgrade? Thanks.

List all domains?

How to make domain argument optional? For example, DreamHost API list query contains all domains configured for certain account. Also, what is the 'domain' lexicon? Is it zone or record?

[{u'account_id': u'102',
  u'comment': u'',
  u'editable': u'0',
  u'record': u'argo.net',
  u'type': u'A',
  u'value': u'192.168.1.1',
  u'zone': argo.net'},
 {u'account_id': u'102',
  u'comment': u'',
  u'editable': u'0',
  u'record': u'argomail.net',
  u'type': u'MX',
  u'value': u'0 aspmx.l.google.com.',
  u'zone': u'argomail.net'},
...

nsone -- list index out of range.

using dns-lexicon 2.1.11

I'm getting a list index out of range in the process of cleaning up acme/certbot TXT records on nsone.

/opt/certbot # lexicon nsone list sitelabs.io TXT --auth-token MYTOKEN
Arguments: Namespace(action='list', auth_token='MYTOKEN', content=None, delegated=None, domain='sitelabs.io', identifier=None, name=None, priority=None, provider_name='nsone', ttl=None, type='TXT')
Starting new HTTPS connection (1): api.nsone.net
https://api.nsone.net:443 "GET /v1/zones/sitelabs.io HTTP/1.1" 200 None
Starting new HTTPS connection (1): api.nsone.net
https://api.nsone.net:443 "GET /v1/zones/sitelabs.io HTTP/1.1" 200 None
Traceback (most recent call last):
  File "/usr/local/bin/lexicon", line 11, in <module>
    load_entry_point('dns-lexicon==2.1.11', 'console_scripts', 'lexicon')()
  File "/usr/local/lib/python2.7/site-packages/lexicon/__main__.py", line 69, in main
    client.execute()
  File "/usr/local/lib/python2.7/site-packages/lexicon/client.py", line 44, in execute
    return self.provider.list_records(self.options.get('type'), self.options.get('name'), self.options.get('content'))
  File "/usr/local/lib/python2.7/site-packages/lexicon/providers/nsone.py", line 68, in list_records
    'content': record['short_answers'][0],
IndexError: list index out of range

The current record list for TXT records is (from the nsone panel):

image

Has there been an nsone response format change?

Problem with sub-subdomain

I have a host test.internal.example.com.
I would like to add an entry in the DNS, which is responsible for the internal.example.com dns subzone.
Lexicon will cut the hostname like this:
test.internal + example.com
instead of test + internal.example.com

Would it be possible to correct it?

Namesilo throwing an exception on create if a record already exists

The Namesilo provider halts with an exception on create if a record already exists. The comment suggests that it should be doing nothing if the record already exists.

Let's Encrypt dns-01 validation doesn't seem to work with wildcard A records, so the theory here is to have my hook create an A/AAAA record when issuing a cert. However the exception prevents this from being possible.

LuaDNS gets 400 Client Error: Bad Request

`Namespace(action='create', auth_token='XXXXXXXXXXXXXXXXXXXXXXXX', auth_username='XXXXXXXXXXXXXXXXXXXXXXXX', content='XXXXXXXXXXXXXXXXXXXXXXXX', domain='XXXXXX', identifier=None, name='_XXXXXXXX.', priority=None, provider_name='luadns', ttl=None, type='TXT')

Traceback (most recent call last):
File "/usr/local/bin/lexicon", line 9, in
load_entry_point('dns-lexicon==1.1.17', 'console_scripts', 'lexicon')()
File "/usr/local/lib/python2.7/dist-packages/lexicon/main.py", line 52, in main
client.execute()
File "/usr/local/lib/python2.7/dist-packages/lexicon/client.py", line 28, in execute
return self.provider.create_record(self.options.get('type'), self.options.get('name'), self.options.get('content'))
File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py", line 30, in create_record
payload = self._post('/zones/{0}/records'.format(self.domain_id), {'type': type, 'name': self._fqdn_name(name), 'content': content, 'ttl': self.options.get('ttl',self.default_ttl)})
File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/base.py", line 39, in _post
return self._request('POST', url, data=data, query_params=query_params)
File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/luadns.py", line 110, in _request
r.raise_for_status() # if the request fails for any reason, throw an error.
File "/usr/lib/python2.7/dist-packages/requests/models.py", line 773, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request

`

Unable to create record for delegated subdomain

I am able to use Lexicon to create records on our top level domain (platformservices.io) which is hosted by Gandi, but not our delegated subdomains, hosted in Route53.

I get error StandardError: No domain found

Looking at the Route53 provider, it loops through Route53 zones it has found to see where your domain fits:

https://github.com/AnalogJ/lexicon/blob/master/lexicon/providers/route53.py#L94

But the way the domain name is being parsed means it never gets matched. I added a print line to show the domain it was trying to match to and it showed platformservices.io.

The zone in Route53, however is euw.platformservices.io, so the euw is being stripped in order to find the zone name.

urn:acme:error:unauthorized on dns-01 (letsencrypt.default.sh)

When using the letsencrypt.sh lexicon hook (https://github.com/AnalogJ/lexicon/blob/master/examples/letsencrypt.default.sh), the first in a line of domains always works, but then the 2nd or 3rd alias throws this error. It seems like some timeout issue...

Processing domain1.de with alternative names: www.domain1.de app.domain1.de beta.domain1.de domain2.de www.domain2.de app.domain2.de beta.domain2.de domain3.de www.domain3.de app.domain3.de beta.domain3.de
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for domain1.de...
 + Requesting challenge for www.domain1.de...
 + Requesting challenge for app.domain1.de...
 + Requesting challenge for beta.domain1.de...
 + Requesting challenge for domain2.de...
 + Requesting challenge for www.domain2.de...
 + Requesting challenge for app.domain2.de...
 + Requesting challenge for beta.domain2.de...
 + Requesting challenge for domain3.de...
 + Requesting challenge for www.domain3.de...
 + Requesting challenge for app.domain3.de...
 + Requesting challenge for beta.domain3.de...
deploy_challenge called: domain1.de, someKey, someKey2
lexicon cloudflare create domain1.de TXT --name='acme-challenge.domain1.de.' --content='someKey2'
Namespace(action='create', auth_token=None, auth_username=None, content='someKey2', domain='domain1.de', identifier=None, name='_acme-challenge.domain1.de.', priority=None, provider_name='cloudflare', ttl=None, type='TXT')
No handlers could be found for logger "tldextract"
create_record: True
 + Responding to challenge for domain1.de...
clean_challenge called: domain1.de, someKey, someKey2
Namespace(action='delete', auth_token=None, auth_username=None, content='someKey2', domain='domain1.de', identifier=None, name='_acme-challenge.domain1.de.', priority=None, provider_name='cloudflare', ttl=None, type='TXT')
No handlers could be found for logger "tldextract"
list_records: [{'content': u'someKey2', 'id': u'b86e0031d7eebee5d6af14e7f76123a0', 'type': u'TXT', 'name': u'_acme-challenge.domain1.de', 'ttl': 1}]
[{'content': u'someKey2', 'id': u'b86e0031d7eebee5d6af14e7f76123a0', 'type': u'TXT', 'name': u'_acme-challenge.domain1.de', 'ttl': 1}]
delete_record: True
 + Challenge is valid!
deploy_challenge called: www.domain1.de, someKey3, someKey4
lexicon cloudflare create www.domain1.de TXT --name='acme-challenge.www.domain1.de.' --content='someKey4'
Namespace(action='create', auth_token=None, auth_username=None, content='someKey4', domain='www.domain1.de', identifier=None, name='_acme-challenge.www.domain1.de.', priority=None, provider_name='cloudflare', ttl=None, type='TXT')
No handlers could be found for logger "tldextract"
create_record: True
 + Responding to challenge for www.domain1.de...
clean_challenge called: www.domain1.de, someKey3, someKey4
Namespace(action='delete', auth_token=None, auth_username=None, content='someKey4', domain='www.domain1.de', identifier=None, name='_acme-challenge.www.domain1.de.', priority=None, provider_name='cloudflare', ttl=None, type='TXT')
No handlers could be found for logger "tldextract"
list_records: [{'content': u'someKey4', 'id': u'someID', 'type': u'TXT', 'name': u'_acme-challenge.www.domain1.de', 'ttl': 1}]
[{'content': u'someKey4', 'id': u'someID', 'type': u'TXT', 'name': u'_acme-challenge.www.domain1.de', 'ttl': 1}]
delete_record: True
ERROR: Challenge is invalid! (returned: invalid) (result: {
  "type": "dns-01",
  "status": "invalid",
  "error": {
    "type": "urn:acme:error:unauthorized",
    "detail": "Correct value not found for DNS challenge",
    "status": 403
  },
  "uri": "https://acme-v01.api.letsencrypt.org/acme/challenge/qscr5vea-pExCauIAf3ZeW6GrzDFCfrqENJHluMoe0c/211202993",
  "token": "someKey3",
  "keyAuthorization": "someKey3.someKey5"
})

I´ve replaced keys sent with someKey / someID...

Authentication mechanisms

Hey,

I think this project is very interesting, but your approach on how to manage authentication with all those different providers looks like it will sooner or later be a real pain.

Some providers use different kinds of oauth, some just an API-token, some username+password, some account+subaccount+password, etc.

If you keep the login settings part of the main script I think you'll have a list of way to many confusing options very soon.

I'd suggest moving authentication options into the provider plugins, so that when you use a provider with simple token-auth it only accepts that parameter, and with oauth maybe even provide some kind of wizard that has instructions on how to set up the tool with that certain provider.

Support for DnsMadeEasy internal ANAME record type

An ANAME record in DnsMadeEasy is an alternative to CNAME records, it has the same format.

The difference is that ANAME is dynamically converted to an A record when the DNS server receives a query for the A record. It is a bit faster than CNAME, but its main advantage is that it can be used on the root of the domain. You cannot use a CNAME on example.com, but you can configure an ANAME on it. An ANAME record looks exactly like a CNAME record while it is defined in a zone file (or at least on the dnsmadeeasy UI). For example:
www.example.com. ANAME www1
example.com. ANAME www1

Namecheap support

Guys, what's the status of Namecheap support? I see there's a pretty old branch and even a prototype of sorts committed, is there any roadmap for this to happen? I'd really appreciate it :-)

'No domain found' on Ubuntu for Cloudflare

I'm on Ubuntu 14.04.3 and have installed the prerequesites (pip install --upgrade requests[security] then pip install dns-lexicon), then downloaded your latest dehydrated.default.sh script and placed it in hooks/lexicon/lexicon.sh in the latest cloned dehydrated package.

Then I did:

# export PROVIDER=cloudflare
# export LEXICON_CLOUDFLARE_USERNAME=<MY_CF_EMAIL_LOGIN>
# export LEXICON_CLOUDFLARE_TOKEN=<MY_CF_API_KEY>
# dehydrated/dehydrated -c -t dns-01 -d DOMAIN.TLD -d mail.DOMAIN.TLD -d www.DOMAIN.TLD -k dehydrated/hooks/lexicon/lexicon.sh

And it spits the following:

#
# !! WARNING !! No main config file found, using default config!
#
Processing DOMAIN.TLD with alternative names: mail.DOMAIN.TLD www.DOMAIN.TLD
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for DOMAIN.TLD...
 + Requesting challenge for mail.DOMAIN.TLD...
 + Requesting challenge for www.DOMAIN.TLD...
deploy_challenge called: DOMAIN.TLD, Gm1Xu2MFiLes5LrjumDDWef0CiNj32Dqt5j8LX0JmMU, Za3K2rD4SkrriVceCbDMm_KXIPoc08rx42NTQS2ENZw
Namespace(action='create', auth_token=None, auth_username=None, content='Za3K2rD4SkrriVceCbDMm_KXIPoc08rx42NTQS2ENZw', domain='DOMAIN.TLD', identifier=None, name='_acme-challenge.DOMAIN.TLD.', priority=None, provider_name='cloudflare', ttl=None, type='TXT')
Traceback (most recent call last):
  File "/usr/local/bin/lexicon", line 11, in <module>
    load_entry_point('dns-lexicon==1.1.20', 'console_scripts', 'lexicon')()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/__main__.py", line 52, in main
    client.execute()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/client.py", line 25, in execute
    self.provider.authenticate()
  File "/usr/local/lib/python2.7/dist-packages/lexicon/providers/cloudflare.py", line 24, in authenticate
    raise StandardError('No domain found')
StandardError: No domain found

Didn't look in your python code myself yet.

Gandi Records not picking up TTL

I'm trying to renew Lets Encrypt Certs using dehydrated, using lexicon to handle DNS modifications on a Gandi domain. New challenge TXT records are being created with a TTL of 3 hours. I have tried setting --ttl manually but they're always created with the default 3 hour TTL:

lexicon gandi create platformservices.io TXT --name="testsubdomain.platformservices.io" --content="TEST" --ttl="120"

Looks to me like the create_record method is missing the ttl property:

https://github.com/AnalogJ/lexicon/blob/master/lexicon/providers/gandi.py#L93

route53: boto3 should not be optional

I've found that pip install dns-lexicon[route53] does not install the required dep boto3. See error below. Example use/workaround can be found at https://github.com/willfarrell/docker-letsencrypt/blob/master/Dockerfile

Namespace(action='list', auth_access_key=None, auth_access_secret=None, auth_token=None, auth_username=None, content=None, domain='ductoid.com', identifier=None, name=None, priority=None, provider_name='route53', ttl=None, type='A')
Traceback (most recent call last):
  File "/usr/bin/lexicon", line 11, in <module>
    load_entry_point('dns-lexicon==1.1.20', 'console_scripts', 'lexicon')()
  File "/usr/lib/python2.7/site-packages/lexicon/__main__.py", line 51, in main
    client = Client(parsed_args.__dict__)
  File "/usr/lib/python2.7/site-packages/lexicon/client.py", line 22, in __init__
    self.provider = provider_class(self.options)
  File "/usr/lib/python2.7/site-packages/lexicon/providers/route53.py", line 80, in __init__
    self.r53_client = boto3.client(
NameError: global name 'boto3' is not defined

I would submit a PR, but python is a little out of my wheelhouse.

SPF support could be confusing

I note that the SPF RR type was discontinued in RFC 7208 (see https://tools.ietf.org/html/rfc7208#section-3.1). I tried searching for my own SPF records with the SPF type, but of course they were not found (they do show up as TXT records, which they are). This could lead to confusion as to whether the command should return SPF records (which are implemented as standard TXT records) or records of the SPF RR type (which has been discontinued).

For clarity, I would suggest that lexicon drop the SPF RR type. Alternately, you could allow listing the SPF RR type (possibly with a printed message for the user that notes that SPF records implemented as TXT records will not be returned), and disallow creation of SPF RRs (since current RFC specifies that it must be implemented by TXT record only).

docs: Certbot documentation

Hi, love the idea and execution of your project.

At the moment your README primarily only refers to dehydrated as an ACME client.

IME the majority of users of Let's Encrypt end up seeing and trying Certbot before any other client. I think it would be tremendously helpful to both projects if lexicon could include some words on how to use it with Certbot.

I've written a guide of sorts here but it might be even better to just include an example Certboot hook in the README or GitHub wiki or whatever.

Curious to hear what sounds good to the maintainers.

Python 3 compatibility.

I hit the Missing parentheses in call to 'print' error after pip install with python 3.5, is this a known issue or is not Python 3 supported?

letsencrypt.sh + lexicon on DNSimple creates _acme-challenge.www.foo.net.foo.net

Thanks for writing this library (and blog post) just in time, it helped me get Let's Encrypt certs a few days ago :-)

Using it with DNSimple I had to intervene in the code, as it was creating wrong domains:
I'm controlling mathdown.net zone;
for www.mathdown.net it created _acme-challenge.www.mathdown.net.mathdown.net,
for mathdown.net it created _acme-challenge.mathdown.net.mathdown.net.

The command executed by the hook was:

lexicon dnsimple create www.mathdown.net TXT --name _acme-challenge.www.mathdown.net. --content IquLz5DkyqvV1f37rpP4sO2IkLp6HQKDX0psSt_8aFI

Output, with printing and breakpoint in providers/dnsimple.py in _request():

Namespace(action='create', auth_otp_token=None, auth_password=None, auth_token='[REDACTED]', auth_username='[email protected]', content='kDA2FinGwvNPkcn6QzbrGS-q1q6fzSmiQL2EkpZPRrc', domain='www.mathdown.net', identifier=None, name='_acme-challenge.www.mathdown.net.', priority=None, provider_name='dnsimple', ttl=None, type='TXT')

> /home/beni/.local/lib/python2.7/site-packages/lexicon/providers/dnsimple.py(120)_request()
-> r = requests.request(action, self.api_endpoint + url, params=query_params,
(Pdb) pp locals()
{'action': 'GET',
 'data': {},
 'default_auth': None,
 'default_headers': {'Accept': 'application/json',
                     'Content-Type': 'application/json',
                     'X-DNSimple-Token': '[email protected]:[REDACTED]'},
 'pprint': <module 'pprint' from '/usr/lib/python2.7/pprint.pyc'>,
 'query_params': {},
 'self': <lexicon.providers.dnsimple.Provider object at 0x7f201d837310>,
 'url': '/domains/mathdown.net'}
(Pdb) c
> /home/beni/.local/lib/python2.7/site-packages/lexicon/providers/dnsimple.py(120)_request()
-> r = requests.request(action, self.api_endpoint + url, params=query_params,
(Pdb) pp locals()
{'action': 'POST',
 'data': {'record': {'content': 'IquLz5DkyqvV1f37rpP4sO2IkLp6HQKDX0psSt_8aFI',
                     'name': '_acme-challenge.www.mathdown.net',
                     'record_type': 'TXT'}},
 'default_auth': None,
 'default_headers': {'Accept': 'application/json',
                     'Content-Type': 'application/json',
                     'X-DNSimple-Token': '[email protected]:[REDACTED]'},
 'pdb': <module 'pdb' from '/usr/lib/python2.7/pdb.pyc'>,
 'pprint': <module 'pprint' from '/usr/lib/python2.7/pprint.pyc'>,
 'query_params': {},
 'self': <lexicon.providers.dnsimple.Provider object at 0x7f201d837310>,
 'url': '/domains/mathdown.net/records'}
(Pdb) self.options.domain
'mathdown.net'

and for mathdown.net:

+ lexicon dnsimple create mathdown.net TXT --name _acme-challenge.mathdown.net. --content NonxWWUM3KwgrW3rOTQyRnrzP-IHqEJRPedPpia1HbE
...
{'action': 'GET',
...
{'action': 'POST',
 'data': {'record': {'content': 'NonxWWUM3KwgrW3rOTQyRnrzP-IHqEJRPedPpia1HbE',
                     'name': '_acme-challenge.mathdown.net',
                     'record_type': 'TXT'}},
 'default_auth': None,
 'default_headers': {'Accept': 'application/json',
                     'Content-Type': 'application/json',
                     'X-DNSimple-Token': '[email protected]:[REDACTED]'},
 'name': '_acme-challenge',
 'pprint': <module 'pprint' from '/usr/lib/python2.7/pprint.pyc'>,
 'query_params': {},
 'self': <lexicon.providers.dnsimple.Provider object at 0x7fb466b51310>,
 'url': '/domains/mathdown.net/records'}
(Pdb) p self.options.domain
'mathdown.net'

So what's going on?
data['record']['name'] is taken by DNSimple as-is, but its meaning is relative to the zone.
I needed to truncate it to _acme-challenge.www for www.mathdown.net and _acme-challenge for mathdown.net.

Why Cloudflare worked in your blog post? It seems its API takes absolute name (https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record — note --data '{"type":"A","name":"example.com","content":"127.0.0.1","ttl":120}' in their example),
while DNSimple takes relative name (https://developer.dnsimple.com/v1/domains/records/#create — note "name": "" in their example).

=> It seems like providers/dnsimple.py is the one that should truncate names in most methods?
(And list.records should append zone back to names?)

Add Dreamhost API support

Hi AnalogJ,

I have been looking at lexicon and your blog posts about using Let's Encrypt for private (firewalled) servers. However, I'm lost at how I could include Dreamhost support - I've been looking at the config files for the other providers but wasn't able to decypher how I would need to create one from scratch...

Dreamhost API details are here: https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands

Any advice you could give me where to start?
Thanks a heap in advance!!

Digitalocean Provider spits out invalid json

[  
   {  
      'content':u'"v=DKIM1; k=rsa; p=MIGfMA0GC[...redacted...]GUrwFVgQIDAQAB"',
      'id':1256011,
      'type':u'TXT',
      'name':'mesmtp._domainkey.ctnguyen.net',
      'ttl':''
   },
   {  
      'content':      u'"v=spf1 include:spf.messagingengine.com -all"',
      'id':2349116,
      'type':u'TXT',
      'name':'@.ctnguyen.net',
      'ttl':''
   }
]

The issued command is lexicon digitalocean list ctnguyen.net --auth-token $LEXICON_DIGITALOCEAN_TOKEN TXT

Look after the content-pair and type-pair. There are u's after the colon. I don't were there are coming from.
I discovered this, when I tried to setup letsencrypt.sh with the provided example hook.
The clean_challenge returns an empty list(maybe because the json is invalid).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.