From 9a7ed57a3f973e8cd3af61ded71eb2ca76b028a8 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Wed, 17 May 2023 10:14:26 -0400 Subject: [PATCH 1/2] Cache cert verification methods (#871) * cache cert verification CheckSignature and Verify are expensive methods, and certificates are static. Cache the results. * use atomics * make sure public key bytes match * add VerifyWithCache and ResetCache * cleanup * use VerifyWithCache * doc --- cert/ca.go | 10 ++++-- cert/cert.go | 72 +++++++++++++++++++++++++++++++++++++++++-- connection_manager.go | 2 +- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/cert/ca.go b/cert/ca.go index d700544..0ffbd87 100644 --- a/cert/ca.go +++ b/cert/ca.go @@ -91,9 +91,15 @@ func (ncp *NebulaCAPool) ResetCertBlocklist() { ncp.certBlocklist = make(map[string]struct{}) } -// IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted +// NOTE: This uses an internal cache for Sha256Sum() that will not be invalidated +// automatically if you manually change any fields in the NebulaCertificate. func (ncp *NebulaCAPool) IsBlocklisted(c *NebulaCertificate) bool { - h, err := c.Sha256Sum() + return ncp.isBlocklistedWithCache(c, false) +} + +// IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted +func (ncp *NebulaCAPool) isBlocklistedWithCache(c *NebulaCertificate, useCache bool) bool { + h, err := c.sha256SumWithCache(useCache) if err != nil { return true } diff --git a/cert/cert.go b/cert/cert.go index 8e5dbb9..24a75e3 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -17,6 +17,7 @@ import ( "math" "math/big" "net" + "sync/atomic" "time" "golang.org/x/crypto/curve25519" @@ -42,6 +43,14 @@ const ( type NebulaCertificate struct { Details NebulaCertificateDetails Signature []byte + + // the cached hex string of the calculated sha256sum + // for VerifyWithCache + sha256sum atomic.Pointer[string] + + // the cached public key bytes if they were verified as the signer + // for VerifyWithCache + signatureVerified atomic.Pointer[[]byte] } type NebulaCertificateDetails struct { @@ -562,6 +571,27 @@ func (nc *NebulaCertificate) CheckSignature(key []byte) bool { } } +// NOTE: This uses an internal cache that will not be invalidated automatically +// if you manually change any fields in the NebulaCertificate. +func (nc *NebulaCertificate) checkSignatureWithCache(key []byte, useCache bool) bool { + if !useCache { + return nc.CheckSignature(key) + } + + if v := nc.signatureVerified.Load(); v != nil { + return bytes.Equal(*v, key) + } + + verified := nc.CheckSignature(key) + if verified { + keyCopy := make([]byte, len(key)) + copy(keyCopy, key) + nc.signatureVerified.Store(&keyCopy) + } + + return verified +} + // Expired will return true if the nebula cert is too young or too old compared to the provided time, otherwise false func (nc *NebulaCertificate) Expired(t time.Time) bool { return nc.Details.NotBefore.After(t) || nc.Details.NotAfter.Before(t) @@ -569,7 +599,26 @@ func (nc *NebulaCertificate) Expired(t time.Time) bool { // Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc) func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error) { - if ncp.IsBlocklisted(nc) { + return nc.verify(t, ncp, false) +} + +// VerifyWithCache will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc) +// +// NOTE: This uses an internal cache that will not be invalidated automatically +// if you manually change any fields in the NebulaCertificate. +func (nc *NebulaCertificate) VerifyWithCache(t time.Time, ncp *NebulaCAPool) (bool, error) { + return nc.verify(t, ncp, true) +} + +// ResetCache resets the cache used by VerifyWithCache. +func (nc *NebulaCertificate) ResetCache() { + nc.sha256sum.Store(nil) + nc.signatureVerified.Store(nil) +} + +// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc) +func (nc *NebulaCertificate) verify(t time.Time, ncp *NebulaCAPool, useCache bool) (bool, error) { + if ncp.isBlocklistedWithCache(nc, useCache) { return false, ErrBlockListed } @@ -586,7 +635,7 @@ func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error return false, ErrExpired } - if !nc.CheckSignature(signer.Details.PublicKey) { + if !nc.checkSignatureWithCache(signer.Details.PublicKey, useCache) { return false, ErrSignatureMismatch } @@ -809,6 +858,25 @@ func (nc *NebulaCertificate) Sha256Sum() (string, error) { return hex.EncodeToString(sum[:]), nil } +// NOTE: This uses an internal cache that will not be invalidated automatically +// if you manually change any fields in the NebulaCertificate. +func (nc *NebulaCertificate) sha256SumWithCache(useCache bool) (string, error) { + if !useCache { + return nc.Sha256Sum() + } + + if s := nc.sha256sum.Load(); s != nil { + return *s, nil + } + s, err := nc.Sha256Sum() + if err != nil { + return s, err + } + + nc.sha256sum.Store(&s) + return s, nil +} + func (nc *NebulaCertificate) MarshalJSON() ([]byte, error) { toString := func(ips []*net.IPNet) []string { s := []string{} diff --git a/connection_manager.go b/connection_manager.go index a754a84..528cf1b 100644 --- a/connection_manager.go +++ b/connection_manager.go @@ -427,7 +427,7 @@ func (n *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostIn return false } - valid, err := remoteCert.Verify(now, n.intf.caPool) + valid, err := remoteCert.VerifyWithCache(now, n.intf.caPool) if valid { return false } From 7ae3cd25f80d87cdb7c0d3c6ecd9be447af46535 Mon Sep 17 00:00:00 2001 From: Wade Simmons Date: Wed, 17 May 2023 11:02:53 -0400 Subject: [PATCH 2/2] v1.7.0 (#870) Update CHANGELOG for Nebula v1.7.0 --- CHANGELOG.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c4c00..3febb32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.7.0] - 2023-05-17 + ### Added + - `nebula-cert ca` now supports encrypting the CA's private key with a passphrase. Pass `-encrypt` in order to be prompted for a passphrase. Encryption is performed using AES-256-GCM and Argon2id for KDF. KDF parameters default to RFC recommendations, but can be overridden via CLI - flags `-argon-memory`, `-argon-parallelism`, and `-argon-iterations`. + flags `-argon-memory`, `-argon-parallelism`, and `-argon-iterations`. (#386) + +- Support for curve P256 and BoringCrypto has been added. See README section + "Curve P256 and BoringCrypto" for more details. (#865, #861, #769, #856, #803) + +- New firewall rule `local_cidr`. This could be used to filter destinations + when using `unsafe_routes`. (#507) + +- Add `unsafe_route` option `install`. This controls whether the route is + installed in the systems routing table. (#831) + +- Add `tun.use_system_route_table` option. Set to true to manage unsafe routes + directly on the system route table with gateway routes instead of in Nebula + configuration files. This is only supported on Linux. (#839) + +- The metric `certificate.ttl_seconds` is now exposed via stats. (#782) + +- Add `punchy.respond_delay` option. This allows you to change the delay + before attempting punchy.respond. Default is 5 seconds. (#721) + +- Added SSH commands to allow the capture of a mutex profile. (#737) + +- You can now set `lighthouse.calculated_remotes` to make it possible to do + handshakes without a lighthouse in certain configurations. (#759) + +- The firewall can be configured to send REJECT replies instead of the default + DROP behavior. (#738) + +- For macOS, an example launchd configuration file is now provided. (#762) + +### Changed + +- Lighthouses and other `static_host_map` entries that use DNS names will now + be automatically refreshed to detect when the IP address changes. (#796) + +- Lighthouses send ACK replies back to clients so that they do not fall into + connection testing as often by clients. (#851, #408) + +- Allow the `listen.host` option to contain a hostname. (#825) + +- When Nebula switches to a new certificate (such as via SIGHUP), we now + rehandshake with all existing tunnels. This allows firewall groups to be + updated and `pki.disconnect_invalid` to know about the new certificate + expiration time. (#838, #857, #842, #840, #835, #828, #820, #807) + +### Fixed + +- Always disconnect blocklisted hosts, even if `pki.disconnect_invalid` is + not set. (#858) + +- Dependencies updated and go1.20 required. (#780, #824, #855, #854) + +- Fix possible race condition with relays. (#827) + +- FreeBSD: Fix connection to the localhost's own Nebula IP. (#808) + +- Normalize and document some common log field values. (#837, #811) + +- Fix crash if you set unlucky values for the firewall timeout configuration + options. (#802) + +- Make DNS queries case insensitive. (#793) + +- Update example systemd configurations to want `nss-lookup`. (#791) + +- Errors with SSH commands now go to the SSH tunnel instead of stderr. (#757) + +- Fix a hang when shutting down Android. (#772) ## [1.6.1] - 2022-09-26 @@ -405,7 +475,8 @@ created.) - Initial public release. -[Unreleased]: https://github.com/slackhq/nebula/compare/v1.6.1...HEAD +[Unreleased]: https://github.com/slackhq/nebula/compare/v1.7.0...HEAD +[1.7.0]: https://github.com/slackhq/nebula/releases/tag/v1.7.0 [1.6.1]: https://github.com/slackhq/nebula/releases/tag/v1.6.1 [1.6.0]: https://github.com/slackhq/nebula/releases/tag/v1.6.0 [1.5.2]: https://github.com/slackhq/nebula/releases/tag/v1.5.2