-
-
Notifications
You must be signed in to change notification settings - Fork 15k
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
Replace simp-le with lego and support DNS-01 challenge #77578
Conversation
Lego allows users to use the DNS-01 challenge to validate their certificates. It is mostly backwards compatible, with a few caveats. - extraDomains can no longer have different webroots to the main webroot for the cert. - An email address is now mandatory for account creation The following other changes were required: - Deprecate security.acme.certs.<name>.plugins, as this was specific to simp-le - Rename security.acme.validMin to validMinDays, to avoid confusion and errors. Lego requires the TTL to be specified in days - Add options to cover DNS challenge (dnsProvider, credentialsFile, dnsPropagationCheck) - A shared state directory is now used (/var/lib/acme/.lego) to avoid account creation rate limits and share credentials between certs
nixos/modules/security/acme.nix
Outdated
++ concatMap (p: [ "-f" p ]) data.plugins | ||
++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains) | ||
email = if data.email == null then cfg.email else data.email; | ||
globalOpts = [ "-d" data.domain "--email" email "--path" "." ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick note on --path .
. Somewhere between lego 3.0.2 and lego 3.2.0, the default path changed from ./.lego
to ~/.lego
. I had to add this statement so that lego always runs in the service's WorkingDirectory
.
Ping @aanderse Finally, the PR is here. |
Thanks @m1cr0man! This is great work. I hope to review and test this over the next few weeks, switching some of my test (and hopefully production) systems over to this. |
I will have a ook at this Tuesday. Thanks for the work |
# Only try loading the credentialsFile if the dns challenge is enabled | ||
EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null; | ||
ExecStart = pkgs.writeScript "acme-start" '' | ||
#!${pkgs.runtimeShell} -e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it not possible to do what we want in one run of the binary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not look like it does: go-acme/lego#216
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to enforce the validMinDays
you have to run renew instead of run, which seems like an oversight in lego honestly. Run creates the account credentials if they don't exist but forces a new cert to be generated. I was tempted to add explicit logic to check for the cert's credentials files but I feel that's too solution specific. This conditional logic is the same as in the other PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple notes as I work through this...
Some issues with webroot
at the moment. The tmpfiles
rules reference webroot
but for DNS certs this won't be defined.
@@ -173,6 +189,15 @@ in | |||
''; | |||
}; | |||
|
|||
acceptTerms = mkOption { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do other people think of this? Personally I think it is overkill... but am fairly indifferent I guess 🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Let's Encrypt wants users to accept their terms I don't think implicitly accepting it for endusers should be the way to go. This knob is the exact place where we can point users to the terms of services of Lets Encrypt.
This doesn't make much sense though when overriding the API endpoints (-s|--server
) because then we can't know where the correct ToS are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a really strong feeling about this. We were implicitly accepting it before no? This seems to make the behaviour slightly better than before
If anyone can help me on my two todo's mentioned in the description that would be great. I tried adding a regular mkRemovedOptionModule under certOpts, with a relative attribute path. In order to change extraDomains I tried mkChangedOptionModule but I doubt this works since the attribute path has not changed, and the warning it would produce wouldn't explain the change to a list. cfg = config.security.acme;
certOpts = { name, ... }: {
+ imports = [
+ (mkRemovedOptionModule [ "plugins" ] ''
+ This option has been removed since simp-le was replaced with LEGo.
+ You may be able to achieve the desired functionality with postRun
+ '')
+ (mkChangedOptionModule [ "extraDomains" ] [ "extraDomains" ] (config: let
+ domains = config.security.acme."${name}".extraDomains;
+ in if isList domains then domains else attrNames domains))
+ ];
+ These are the errors I got: The option `security.acme.certs.mydomain.warnings' defined in `/root/nixpkgs/nixos/modules/security/acme.nix' does not exist.
# Removing the mkChangedOptionModule....
The option `security.acme.certs.mydomain.assertions' defined in `/root/nixpkgs/nixos/modules/security/acme.nix' does not exist. |
@m1cr0man this is what @infinisil had to say on the matter: https://logs.nix.samueldr.com/nixos-dev/2020-01-15#1579087078-1579096064 |
It also seems that simp-le still uses acmev1 which will be deprecated by letsencrypt in June. |
I'm very happy with the new DNS challenge functionality. After a few minor changes I mentioned it worked flawlessly. Next step is to test migration of existing HTTP challenge based certificates. Great work @m1cr0man! |
@aanderse how do you envision that migration path? for example; would keeping around account information and private keys from simp_le around be a requirement for you? |
@arianvp Those are already being kept around. I'm not explicitly deleting the JSON files in |
Sorry for the triple-comment, but I just want to point out that I never figured out how to add the assertions to the submodule. The |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/nixos-20-03-feature-freeze/5655/27 |
Ok it was super difficult to figure out, but I got the DNS-01 challenge test working. However in doing this I've somehow broken the test for b.example.com and I'm not sure how. Would someone else be able to take a look at it(@arianvp)? |
Fixed it - http-01 validation was running before nginx was started. :) This is ready for review again |
user = config.services.nginx.user; | ||
group = config.services.nginx.group; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary? Can't we use the per-vhost enableACME
option in the nginx module, and just reconfigure the dns provider in the acme module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm testing wildcard certs. You can't specify a wildcard cert through enableACME, and that option is tested elsewhere. Personally I use the cert for more than just nginx so I want to ensure it works hard way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure - I'd expect serverAliases
to work with that - if not, I'd consider it a bug.
I just find this test hard to understand. - If we can't use the nginx-provided ACME functionalities, probably it'd be more understandable to just not use nginx in the test, only configure the acme module, and check test certificate files appear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's sufficient to simply check does the file exist to truly test wildcard certs. It should work when used with any service (in this case, nginx) serving under that domain. Using nginx here keeps things consistent with the other tests. You're right I could've jused used serverAliases
and added *.example.com
there and it would've worked fine, but I'm testing the acme module, not nginx.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant using nginx, but not the acme-specific support of it makes the test much more verbose. Maybe a nginx test withserverAliases
, and a simple one which just looks at the certificate via openssl x509 -in full.pem -text -noout | grep DNS
?
user = config.services.nginx.user; | ||
group = config.services.nginx.group; | ||
}; | ||
systemd.targets."acme-finished-example.com" = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we do that? This looks like you're workarounding something…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a comment on the earlier ones. It's so that it can be waited upon in the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please copy that comment over - I got drowned in the rest.
systemd.services."acme-example.com" = { | ||
wants = [ "acme-finished-example.com.target" ]; | ||
before = [ "acme-finished-example.com.target" "nginx.service" ]; | ||
wantedBy = [ "nginx.service" ]; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here - I wouldn't expect to have to specify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nginx was starting after the certs consistently when I was testing, causing the HTTP-01 validation to fail. Roll it back and run nix-build acme.nix
and you'll see what I mean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's only for the wildcard certificate, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it was for the HTTP-01 challenge certs, aka the ones using enableACME
wantedBy = [ "nginx.service" ]; | ||
}; | ||
services.nginx.virtualHosts."c.example.com" = { | ||
forceSSL = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can't we use enableACME
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm testing wildcard certs. I don't want a cert for c.example.com
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above.
sslCertificate = config.security.acme.certs."example.com".directory + "/cert.pem"; | ||
sslTrustedCertificate = config.security.acme.certs."example.com".directory + "/full.pem"; | ||
sslCertificateKey = config.security.acme.certs."example.com".directory + "/key.pem"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is then probably not necessary anymore, too.
Merge remote-tracking branch 'remotes/upstream/master'
user = config.services.nginx.user; | ||
group = config.services.nginx.group; | ||
}; | ||
systemd.targets."acme-finished-example.com" = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please copy that comment over - I got drowned in the rest.
user = config.services.nginx.user; | ||
group = config.services.nginx.group; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure - I'd expect serverAliases
to work with that - if not, I'd consider it a bug.
I just find this test hard to understand. - If we can't use the nginx-provided ACME functionalities, probably it'd be more understandable to just not use nginx in the test, only configure the acme module, and check test certificate files appear.
systemd.services."acme-example.com" = { | ||
wants = [ "acme-finished-example.com.target" ]; | ||
before = [ "acme-finished-example.com.target" "nginx.service" ]; | ||
wantedBy = [ "nginx.service" ]; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's only for the wildcard certificate, right?
systemd.targets."acme-finished-b.example.com" = {}; | ||
systemd.services."acme-b.example.com" = { | ||
wants = [ "acme-finished-b.example.com.target" ]; | ||
before = [ "acme-finished-b.example.com.target" ]; | ||
after = [ "nginx.service" ]; | ||
}; | ||
services.nginx.virtualHosts."b.example.com" = { | ||
enableACME = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should name these "subdomain certs" a bit more self-explaining, like "webroot-nginx" maybe?
wantedBy = [ "nginx.service" ]; | ||
}; | ||
services.nginx.virtualHosts."c.example.com" = { | ||
forceSSL = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see above.
@m1cr0man are you going to be able to get this addressed before feature freeze tomorrow? |
This is mostly me being picky with the tests being a bit too complicated for my taste - the module itself was already tested in "production" environments.
FWIW, this can and should be merged for 20.03 - the tests can be improved later on, too.
|
@flokli in general I found these acme tests to be a bit too complex. I would love to rewrite the whole lot of them and incorporate some Apache based tests too, but I can't do that today. I feel that given that there is at least a working test for DNS-01 and the numerous production instances of this code now running that it is stable for 20.03 |
Yep LGTM and improve the test suite at a later stage
…On Mon, Feb 10, 2020, 09:15 Lucas Savva ***@***.***> wrote:
@flokli <https://github.com/flokli> in general I found these acme tests
to be a bit too complex. I would love to rewrite the whole lot of them and
incorporate some Apache based tests too, but I can't do that today. I feel
that given that there is at least a working test for DNS-01 and the
numerous production instances of this code now running that it is stable
for 20.03
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#77578?email_source=notifications&email_token=AAEZNI2GHLEBHL6W6FGFFMTRCEEIRA5CNFSM4KF2BI5KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOELHTF7I#issuecomment-584004349>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEZNI7BVEKDKZMO2HZKIJTRCEEIRANCNFSM4KF2BI5A>
.
|
Okay, let's merge this, and improve the tests later on. |
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
Motivation for this change
Continuation of #63613
Closes #34941, #35168
I have the following things still to do:
security.acme.certs.<name>.plugins
(I don't know how, need help)mkChangedOptionModule
to convertextraDomains
to a list since custom webroots are no longer possible.It is mostly backwards compatible, with a few caveats.
extraDomains
can no longer have different webroots to the main webroot for the cert.security.acme.email
to make transitioning easier, and an assertion.security.acme.acceptTerms
and defaulted it totrue
to avoid breaking user's configs. There may be reason to change this default depending on what reviewers think.The following other changes were required:
security.acme.certs.<name>.plugins
, as this was specific to simp-lesecurity.acme.validMin
tovalidMinDays
, to avoid confusion and errors. Lego requires the TTL to be specified in days. I have added amkChangedOptionModule
to cover this.dnsProvider
,credentialsFile
,dnsPropagationCheck
)/var/lib/acme/.lego
) to avoid account creation rate limits and share credentials between certsI have tested converting my own simp-le generated certs to lego and it worked fine. I also have lego in production generating 28 HTTP validated certs and a DNS-01 cert with a bind DNS server (also on a NixOS box 😄 ). This is a non-breaking change if you haven't used custom webroots or plugins.
Also I have tried to keep the diff as small as possible but that other PR was so old it took me a full day to manually rebase on top of all the more recent changes to the ACME module. I'd be tempted to do some other cleanup to the module but there's good git blame as it is now.
Things done
sandbox
innix.conf
on non-NixOS linux)nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
./result/bin/
)nix path-info -S
before and after)