Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enh] Certificate management integration (e.g. Let's Encrypt ...) #180

Merged
merged 82 commits into from
Nov 28, 2016
Merged

[enh] Certificate management integration (e.g. Let's Encrypt ...) #180

merged 82 commits into from
Nov 28, 2016

Conversation

alexAubin
Copy link
Member

Hi guys,

here's a first proposition of integration of Let's Encrypt (and more generally certificate management).

It adds the following features :

Check certificate status

usage: yunohost domain cert-status [-h] [--full] [domainList [domainList ...]]

positional arguments:
  domainList  Domains to check

optional arguments:
  -h, --help  show this help message and exit
  --full      Show more details

Install Let's Encrypt certificates

usage: yunohost domain cert-install [-h] [--force] [--no-checks]
                                    [--self-signed]
                                    [domainList [domainList ...]]

positional arguments:
  domainList     Domains for which to install the certificates

optional arguments:
  -h, --help     show this help message and exit
  --force        Install even if current certificate is not self-signed
  --no-checks    Does not perform any check that your domain seems correcly
                 configured (DNS, reachability) before attempting to install.
                 (Not recommended)
  --self-signed  Install self-signed certificate instead of Let's Encrypt

Renew Let's Encrypt certificate

usage: yunohost domain cert-renew [-h] [--force] [--email] [--no-checks]
                                  [domainList [domainList ...]]

positional arguments:
  domainList   Domains for which to renew the certificates

optional arguments:
  -h, --help   show this help message and exit
  --force      Ignore the validity treshold (30 days)
  --email      Send an email to root with logs if some renewing fails
  --no-checks  Does not perform any check that your domain seems correcly
               configured (DNS, reachability) before attempting to renew. (Not
               recommended)

Some more details / explanations

  • Let's Encrypt certificates are managed using acme-tiny. The choice was driven by the fact that it is a lightweight module, somewhat easy to use, and can be installed through pip install acme-tiny ;
  • I edited the ssowatconf function to automatically authorize the ACME challenge uri /.well-known/blahif it is present in the nginx conf ;
  • cert-install automatically adds a small cron-job (2 lines of code), calling yunohost domain cert-renew --email every week, which will renew Let's Encrypt certificates which are about to expire, and send an email to root if some renewing fails ;
  • By default, cert-install and cert-renew include some checks that the domain seems to have a reasonable setup to attempt Let's Encrypt certificate installation (DNS poiting to global IP, + being able to GET a page) ;
  • I moved the generation of self-signed certificate into a new file, and cert-install can be used to regenerate a self-signed certificate (in case you don't want Let's Encrypt anymore for some reason). The short/mid-term goal is also to refactorize this part of code to get rid of os.system() calls and use the OpenSSL.crypto module instead.

There are more features we can think about including in the future, such as supporting other ACME authorities, supporting DNS-based challenge, set level of security (e.g. deactivate TLS v1.0 and 1.1), import certificates from non-ACME authorities, provide a diagnosis for domain (e.g. inform user a Let's Encrypt cert can be installed, display current SSL Labs rating) etc... But for now, let's focus on the basic stuff :)

Remaining work

  • How do we manage acme-tiny (and python-tabulate) as dependencies. At the moment this code does not work if you didn't do a pip install acme-tiny and pip install tabulate for example.
  • How do we make sure the transition from letsencrypt_ynh is smooth
  • ???

Cheers !

@M5oul
Copy link
Member

M5oul commented Oct 29, 2016

sudo yunohost domain -h
    cert-install        Install Let's Encrypt certificates for given domains
                        (all by default).
    cert-status         List status of current certificates (all by default).
    list                List domains
    remove              Delete domains
    add                 Create a custom domain
    dns-conf            Generate DNS configuration for a domain
    cert-renew          Renew the Let's Encrypt certificates for given domains
                        (all by default).

Could cert-* sub-commands be grouped?
On the actionmap, there are all followed.
Why do they appear in this order?

@M5oul
Copy link
Member

M5oul commented Oct 29, 2016

How do we manage acme-tiny (and python-tabulate) as dependencies. At the moment this code does not work if you didn't do a pip install acme-tiny and pip install tabulate for example.

As said on the chat room, there is three solutions:

Debian packages

That solution would be the best choice.
tabulate and acme-tiny Debian packages are available on Jessie backport and Debian Stretch.
May be we could install them using this or this solutions.

Subtrees

For now, we could add them as subtrees from respective repositories: tabulate and acme-tiny.
They need to move them to right place.

Pip

It's kind of ugly solution:

sudo pip install tabulate acme-tiny

I suggest to use subtrees solution for now, and Debian packages on Debian Stretch.


def _install_cron() :

cron_job_file = "/etc/cron.weekly/certificateRenewer"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename cron file to something more consistent with other yunohost cron. Currently we have /etc/cron.d/yunohost-applist-yunohost, /etc/cron.d/yunohost-firewall and /etc/cron.d/yunohost-dyndns.
I don't know if it worth to move this cron to /etc/cron.d as well.

I vote for /etc/cron.d/yunohost-certificate-renew ;)

def _dns_ip_match_public_ip(public_ip, domain) :

try :
r = requests.get("http://dns-api.org/A/"+domain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use https url ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if using 'dnspython' lib is easy, but I'd like to avoid using a third party API. Note that 'dnspython' is package in Debian's python-dns.

@alexAubin
Copy link
Member Author

@M5oul

Could cert-* sub-commands be grouped?
On the actionmap, there are all followed.
Why do they appear in this order?

That seems related to the yaml file being loaded in a dictionnary in moulinette ? See here ?

else :
return True

def _domain_is_accessible_through_HTTP(ip, domain) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test looks really weak, I don't think that this is the good way to do that but I don't have any other in mind right now.

@@ -731,13 +731,7 @@ def _dns_ip_match_public_ip(public_ip, domain):

def _domain_is_accessible_through_HTTP(ip, domain):
try:
# Check HTTP reachability
requests.head("http://" + ip, headers={"Host": domain})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no matter what domain value is, this requests will not return any exception ;)

Maybe we should request /yunohost/admin or /yunohost/api to be sure that domain & ip will match. See https://dev.yunohost.org/issues/638 for example

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on the chat, even requesting /yunohost/admin or /yunohost/api doesn't give a clear indication. Properly diagnosing the status/configuration of a domain seems to be a complicated problem 😛. Let's agree that these are some minimal checks for now, and can be refined later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

if ("urn:acme:error:rateLimited" in str(e)) :
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_hit_rate_limit', domain=domain))
else :
raise
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we raise a MoulinetteError, with something like "Unknown error: ..." ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Fixed in e66a708.

@alexAubin alexAubin changed the title Certificate management integration (e.g. Let's Encrypt ...) [enh] Certificate management integration (e.g. Let's Encrypt ...) Nov 24, 2016
@Psycojoker
Copy link
Member

Warning: we need to merge this into unstable, not into testing (we can still have the discussion and review here)

@M5oul M5oul changed the base branch from testing to unstable November 24, 2016 23:31
@zamentur
Copy link
Member

I have done a test with the presence of letsencrypt_ynh it was ok, the webadmin ask me to uninstall it before. For the cli I have add the answer:

root@yunohost:/vagrant/yunohost# yunohost domain cert-install testle4.grimaud.me
Error: certmanager_old_letsencrypt_app_detected

I have done a test with nginx disable:

root@yunohost:/vagrant/yunohost# yunohost domain cert-install testle4.exemple.com
Error: Certificate installation for testle4.exemple.com failed !
Error: [Errno 22] certmanager_domain_http_not_working

@M5oul
Copy link
Member

M5oul commented Nov 25, 2016

Why displaying WARNING for self-signed certif?

yunohost domain cert-status
certificates: 
  le.moul.re: 
    CA_type: Self-signed
    summary: WARNING
    validity: 3649

Copy link
Member

@M5oul M5oul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works fine on my side.
I tested main commands and usage (not every stranges cases and details).
It could be merge.

@M5oul
Copy link
Member

M5oul commented Nov 25, 2016

The A test check failed for a domain names I used.
With host command the DNS was spread on the DNS resolver of the VM.
Finally, I succeed to install the cert with yunohost domain cert-install --no-check --force.
But, from web admin interface, I could stayed blocked as the button was disabled and I couldn't bypassed it.

@alexAubin
Copy link
Member Author

Why displaying WARNING for self-signed certif?

This is with the assumption that domains in Yunohost are supposed to be suitable for "public" use, not for a private use (i.e. just you, + maybe a few friends ?). If your domain is for private use, good for you : add the cert exception popping in your browser, or download the CA certificate - done, ignore the warning.

But if your domain is meant to be visited by some other, nontechnical people, (95% of the use case IMHO ?) you don't want them to run into the spooky warning of all modern browser about self-signed cert, and there's no reason to have a self-cert if you can install a LE cert with one command or two clicks. Hence the choice to report a warning. The meaning of the warning is not really explicit in the CLI, but it is a bit more in the web interface.

@alexAubin
Copy link
Member Author

The A test check failed for a domain names I used.
With host command the DNS was spread on the DNS resolver of the VM.
Finally, I succeed to install the cert with yunohost domain cert-install --no-check --force.
But, from web admin interface, I could stayed blocked as the button was disabled and I couldn't bypassed it.

To me that sounds related to the propagation to the FFDN resolvers. We might actually have a lot of impatient user who run into this if they want their cert ASAP after setting up their domain 😛.

On the CLI you should have seen this message : "The DNS 'A' record for domain {domain:s} is different from this server IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use --no-checks to disable those checks.)".

On the web interface, we could change the message to tell the user to try again later ? But that's in another PR anyway.

(Pro Tip ™️ : https://dnschecker.org/ allows to see the worldwide propagation of a DNS change)

@Psycojoker Psycojoker merged commit 34ca628 into YunoHost:unstable Nov 28, 2016
@alexduf
Copy link

alexduf commented Nov 29, 2016

well done @alexAubin and all! I am super excited to this PR merged ! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants