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

Re-create TLS files if IPs or domains changed #3932

Merged
merged 4 commits into from
Feb 13, 2020

Conversation

halseth
Copy link
Contributor

@halseth halseth commented Jan 20, 2020

This commit makes lnd recreate its TLS certificate if the config's
tlsextradomains or tlsextraips changed. This is useful, since earlier
user would have to manually delete the files to trigger lnd to recreate
them.

The new bahavior is gated behind a new flag --tlsautorefresh

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Solid fix! No major comments on my end after a first pass review. Have reproduced a live scenario to test this fix to ensure that the upgrade to 0.9 will be seamless for any existing mobile apps that package the current form of lnd on mobile?

@@ -100,6 +100,52 @@ func dnsNames(tlsExtraDomains []string) (string, []string) {
return host, dnsNames
}

// Outdated returns whether the given certificate is outdated wrt the IPs and
Copy link
Member

Choose a reason for hiding this comment

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

Naming suggestion: IsOutdated?

Copy link
Member

Choose a reason for hiding this comment

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

wrt -> w.r.t

Copy link
Member

Choose a reason for hiding this comment

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

godoc comment could also use further elaboration on what "outdated" means in this context (IPs or domains aren't a sub-set of what's currently in the cert), as a few of our related packages use this cert package to handle routine cert operations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


// If the certificate has a different number of IP addresses, it is
// definitely out of date.
if len(ips) != len(cert.IPAddresses) {
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, should we relax this to allow more IPs in the cert than are specified in our config? A sample case being a cert that a user has generated themselves, and synced with the existing configuration options.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem then is that even though the user removes an IP from the config, the cert will still allow it.

Copy link
Contributor

Choose a reason for hiding this comment

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

this will return a false positive if the user accidentally includes a duplicate IP, when in fact we don't need to change the cert. imo it'd be better to convert both to sets, then compare length.

one possible downside of this is that certs generated before this may not have checked for duplicates, so it would cause those to be refreshed. perhaps we should start checking for duplicate entries before creating new certs as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, fixed: 7aba914

@@ -744,6 +744,12 @@ func getTLSConfig(tlsCertPath string, tlsKeyPath string, tlsExtraIPs,
return nil, nil, "", err
}
rpcsLog.Infof("Done renewing TLS certificates")

// Reload the certificate data.
certData, _, err = cert.LoadCert(tlsCertPath, tlsKeyPath)
Copy link
Member

Choose a reason for hiding this comment

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

Depending on where we go with this PR, we may want to consider splitting off this commit (either into a new PR or to be cherry-picked onto the next release branch).

Copy link
Contributor

Choose a reason for hiding this comment

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

do we know why our unit tests didn't catch this?

Copy link
Contributor Author

@halseth halseth Feb 12, 2020

Choose a reason for hiding this comment

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

I don't think we have any lnd.go level unit tests?

@Roasbeef Roasbeef added this to the 0.9.1 milestone Jan 21, 2020
@halseth
Copy link
Contributor Author

halseth commented Jan 28, 2020

Gate behind cmd line flag

@halseth halseth force-pushed the tls-oudated-check branch 2 times, most recently from ef5bd29 to 5010ad6 Compare February 5, 2020 14:05
Copy link
Collaborator

@bhandras bhandras left a comment

Choose a reason for hiding this comment

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

LGTM, nice and clean pr, only nits

}

// We expect it to be considered outdated if the IPs or
// domains don't match exaactly what we created.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: exactly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed


// We'll attempt to check up-to-date status for all variants of 1-3
// number of IPs and domains.
for numIPs := 1; numIPs <= 3; numIPs++ {
Copy link
Collaborator

Choose a reason for hiding this comment

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

you could use len(extraIPs) and len(extraDomains) instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is much better indeed!

expected := numIPs != 2 || numDomains != 2
if outdated != expected {
t.Fatalf("expected certificate to be "+
"outdated=%v", expected)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: i'd add "got=%v" to be more informative.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@guggero guggero requested review from cfromknecht and removed request for Roasbeef February 11, 2020 19:14

// If the certificate has a different number of IP addresses, it is
// definitely out of date.
if len(ips) != len(cert.IPAddresses) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this will return a false positive if the user accidentally includes a duplicate IP, when in fact we don't need to change the cert. imo it'd be better to convert both to sets, then compare length.

one possible downside of this is that certs generated before this may not have checked for duplicates, so it would cause those to be refreshed. perhaps we should start checking for duplicate entries before creating new certs as well?

// the order to match exactly, as we assume the same tlsExtraIPs were
// used when the certificate was first created.
for i := range ips {
if !ips[i].Equal(cert.IPAddresses[i]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

related to above, imo order shouldn't matter here. plus we can reuse the sets created above to check. i think it'd be less obvious to users that reordering ip/dns/domains would cause their certs to be renewed

_, dnsNames := dnsNames(tlsExtraDomains)

// If the number of domains are different, it is out of date.
if len(cert.DNSNames) != len(dnsNames) {
Copy link
Contributor

Choose a reason for hiding this comment

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

similar comment here wrt to duplicates/set matching

@@ -744,6 +744,12 @@ func getTLSConfig(tlsCertPath string, tlsKeyPath string, tlsExtraIPs,
return nil, nil, "", err
}
rpcsLog.Infof("Done renewing TLS certificates")

// Reload the certificate data.
certData, _, err = cert.LoadCert(tlsCertPath, tlsKeyPath)
Copy link
Contributor

Choose a reason for hiding this comment

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

do we know why our unit tests didn't catch this?

LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"`
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: recently i think we've been preferring the hyphenated config options, but i see that it would deviate from our other --tls* options :/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, would be nice to move all tls options into its own category, but maybe not for a minor release like this

Copy link
Contributor

Choose a reason for hiding this comment

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

agreed!

This commit creates a new utility method IsOutdated that can be used to
check whether a TLS certificate mathces the extra IPs and domains given
in the lnd config.
This commit makes lnd recreate its TLS certificate if the config's
tlsextradomains or tlsextraips changed. This is useful, since earlier
user would have to manually delete the files to trigger lnd to recreate
them.

To ensure users don't accidentally have their TLS certificate recreated,
we gate it behind a flag --tlsautorefresh that defaults to false.
After renewing the certificate, the new certificate wasn't actually
loaded and used, causing the old one to be used until lnd was restarted.
This fixes that by reloading it after it has been written.
Copy link
Contributor

@cfromknecht cfromknecht left a comment

Choose a reason for hiding this comment

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

non-blocking comment, otherwise LGTM! 🔥

LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"`
Copy link
Contributor

Choose a reason for hiding this comment

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

agreed!


// And that the IPs are considered equal.
if !ip1.Equal(ip2) {
return true, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

is this reachable? is it possible for two IPs to have the same string representation, but be unequal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No idea 😂

@halseth halseth merged commit 8904b68 into lightningnetwork:master Feb 13, 2020
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.

4 participants