Skip to content

Commit

Permalink
Teardown tunnel automatically if peer's certificate expired
Browse files Browse the repository at this point in the history
Handle this with periodic keepalive ticks. This is needed to avoid
hanging a connection even peer's certificate expired.

In case you use short-lived certificates (let's say 2 hours), current
behavior is a bit wrong, because the tunnel stays UP and RUNNING fine
unless restarted nebula service.

This is the log from how it's reflected.

```
time="2021-05-12T10:15:51Z" level=debug msg="Tunnel status" tunnelCheck="map[method:passive state:alive]" vpnIp=172.17.90.241
time="2021-05-12T10:15:59Z" level=debug msg="Tunnel status" tunnelCheck="map[method:passive state:alive]" vpnIp=172.17.90.241
time="2021-05-12T10:16:06Z" level=debug msg="Invalid certificate status" certName=ton31337 vpnIp=172.17.90.241
time="2021-05-12T10:16:06Z" level=debug msg="Tunnel status" tunnelCheck="map[method:passive state:alive]" vpnIp=172.17.90.241
time="2021-05-12T10:16:06Z" level=debug msg="Tunnel status" certName=ton31337 tunnelCheck="map[method:active state:testing]" vpnIp=172.17.90.241
time="2021-05-12T10:16:20Z" level=debug msg="Tunnel status" tunnelCheck="map[method:active state:alive]" vpnIp=172.17.90.241
time="2021-05-12T10:16:20Z" level=info msg="Tunnel status" certName=ton31337 tunnelCheck="map[method:active state:dead]" vpnIp=172.17.90.241
time="2021-05-12T10:16:20Z" level=debug msg="deleting 172.17.90.241 from lighthouse."
time="2021-05-12T10:16:20Z" level=debug msg="Hostmap hostInfo deleted" hostMap="map[indexNumber:3347248980 mapName:main mapTotalSize:844 remoteIndexNumber:3605974939 vpnIp:172.17.90.241]"
```

Signed-off-by: Donatas Abraitis <[email protected]>
  • Loading branch information
ton31337 committed May 12, 2021
1 parent d004fae commit 33a7a59
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 12 deletions.
65 changes: 53 additions & 12 deletions connection_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/cert"
)

// TODO: incount and outcount are intended as a shortcut to locking the mutexes for every single packet
Expand Down Expand Up @@ -153,6 +154,26 @@ func (n *connectionManager) Run() {
}
}

// Check if peer's certificate is not expired or invalid.
func (n *connectionManager) checkToDisconnect(hostinfo *HostInfo) (bool, *cert.NebulaCertificate) {
if !n.intf.disconnectInvalid {
return false, nil
}

if hostinfo == nil {
return false, nil
}

if remoteCert := hostinfo.GetCert(); remoteCert != nil {
valid, _ := remoteCert.Verify(time.Now(), n.intf.caPool)
if !valid {
return true, remoteCert
}
}

return false, nil
}

func (n *connectionManager) HandleMonitorTick(now time.Time, p, nb, out []byte) {
n.TrafficTimer.advance(now)
for {
Expand All @@ -166,25 +187,40 @@ func (n *connectionManager) HandleMonitorTick(now time.Time, p, nb, out []byte)
// Check for traffic coming back in from this host.
traf := n.CheckIn(vpnIP)

// If we saw incoming packets from this ip, just return
hostinfo, err := n.hostMap.QueryVpnIP(vpnIP)

disconnect_invalid, remoteCert := n.checkToDisconnect(hostinfo)
if disconnect_invalid {
n.l.WithField("vpnIp", IntIp(vpnIP)).
WithField("certName", remoteCert.Details.Name).
Debug("Invalid certificate status")
}

// If we saw an incoming packets from this ip and peer's certificate is not
// expired, just ignore.
if traf {
if n.l.Level >= logrus.DebugLevel {
n.l.WithField("vpnIp", IntIp(vpnIP)).
WithField("tunnelCheck", m{"state": "alive", "method": "passive"}).
Debug("Tunnel status")
}
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue

if !disconnect_invalid {
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue
}
}

// If we didn't we may need to probe or destroy the conn
hostinfo, err := n.hostMap.QueryVpnIP(vpnIP)
if err != nil {
n.l.Debugf("Not found in hostmap: %s", IntIp(vpnIP))
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue

if !disconnect_invalid {
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue
}
}

hostinfo.logger(n.l).
Expand Down Expand Up @@ -213,18 +249,23 @@ func (n *connectionManager) HandleDeletionTick(now time.Time) {

vpnIP := ep.(uint32)

hostinfo, err := n.hostMap.QueryVpnIP(vpnIP)

// If we saw incoming packets from this ip, just return
traf := n.CheckIn(vpnIP)
if traf {
n.l.WithField("vpnIp", IntIp(vpnIP)).
WithField("tunnelCheck", m{"state": "alive", "method": "active"}).
Debug("Tunnel status")
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue

disconnect_invalid, _ := n.checkToDisconnect(hostinfo)
if !disconnect_invalid {
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
continue
}
}

hostinfo, err := n.hostMap.QueryVpnIP(vpnIP)
if err != nil {
n.ClearIP(vpnIP)
n.ClearPendingDeletion(vpnIP)
Expand Down
2 changes: 2 additions & 0 deletions examples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pki:
#blocklist is a list of certificate fingerprints that we will refuse to talk to
#blocklist:
# - c99d4e650533b92061b09918e838a5a0a6aaee21eed1d12fd937682865936c72
#disconnect_invalid is a toggle to force a client to be disconnected if the certificate is expired or invalid.
#disconnect_invalid: false

# The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
# A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
Expand Down
3 changes: 3 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type InterfaceConfig struct {
MessageMetrics *MessageMetrics
version string
caPool *cert.NebulaCAPool
disconnectInvalid bool

ConntrackCacheTimeout time.Duration
l *logrus.Logger
Expand All @@ -67,6 +68,7 @@ type Interface struct {
udpBatchSize int
routines int
caPool *cert.NebulaCAPool
disconnectInvalid bool

// rebindCount is used to decide if an active tunnel should trigger a punch notification through a lighthouse
rebindCount int8
Expand Down Expand Up @@ -118,6 +120,7 @@ func NewInterface(c *InterfaceConfig) (*Interface, error) {
writers: make([]*udpConn, c.routines),
readers: make([]io.ReadWriteCloser, c.routines),
caPool: c.caPool,
disconnectInvalid: c.disconnectInvalid,
myVpnIp: ip2int(c.certState.certificate.Details.Ips[0].IP),

conntrackCacheTimeout: c.ConntrackCacheTimeout,
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ func Main(config *Config, configTest bool, buildVersion string, logger *logrus.L
MessageMetrics: messageMetrics,
version: buildVersion,
caPool: caPool,
disconnectInvalid: config.GetBool("pki.disconnect_invalid", false),

ConntrackCacheTimeout: conntrackCacheTimeout,
l: l,
Expand Down

0 comments on commit 33a7a59

Please sign in to comment.