Skip to content

Commit

Permalink
Merge pull request #1758 from /issues/82-optional-encryption
Browse files Browse the repository at this point in the history
Make encryption optional; fixes #82, #1788. LGTM.
  • Loading branch information
bboreham committed Dec 15, 2015
2 parents d1ba4ab + e12dbb4 commit c973808
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 122 deletions.
47 changes: 35 additions & 12 deletions mesh/connection.go
Expand Up @@ -40,17 +40,19 @@ type RemoteConnection struct {
type LocalConnection struct {
sync.RWMutex
RemoteConnection
TCPConn *net.TCPConn
version byte
tcpSender TCPSender
SessionKey *[32]byte
heartbeatTCP *time.Ticker
Router *Router
uid uint64
actionChan chan<- ConnectionAction
errorChan chan<- error
finished <-chan struct{} // closed to signal that actorLoop has finished
OverlayConn OverlayConnection
TCPConn *net.TCPConn
TrustRemote bool // is remote on a trusted subnet?
TrustedByRemote bool // does remote trust us?
version byte
tcpSender TCPSender
SessionKey *[32]byte
heartbeatTCP *time.Ticker
Router *Router
uid uint64
actionChan chan<- ConnectionAction
errorChan chan<- error
finished <-chan struct{} // closed to signal that actorLoop has finished
OverlayConn OverlayConnection
}

type ConnectionAction func() error
Expand Down Expand Up @@ -94,6 +96,7 @@ func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, ro
RemoteConnection: *connRemote, // NB, we're taking a copy of connRemote here.
Router: router,
TCPConn: tcpConn,
TrustRemote: router.Trusts(connRemote),
uid: randUint64(),
actionChan: actionChan,
errorChan: errorChan,
Expand Down Expand Up @@ -189,13 +192,19 @@ func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, errorChan <

conn.Log("connection ready; using protocol version", conn.version)

// only use negotiated session key for untrusted connections
var sessionKey *[32]byte
if conn.Untrusted() {
sessionKey = conn.SessionKey
}

params := OverlayConnectionParams{
RemotePeer: conn.remote,
LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr),
RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr),
Outbound: conn.outbound,
ConnUID: conn.uid,
SessionKey: conn.SessionKey,
SessionKey: sessionKey,
SendControlMessage: conn.sendOverlayControlMessage,
Features: intro.Features,
}
Expand Down Expand Up @@ -253,6 +262,7 @@ func (conn *LocalConnection) makeFeatures() map[string]string {
"ShortID": fmt.Sprint(conn.local.ShortID),
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(conn.uid),
"Trusted": fmt.Sprint(conn.TrustRemote),
}
conn.Router.Overlay.AddFeaturesTo(features)
return features
Expand Down Expand Up @@ -300,6 +310,15 @@ func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
}
}

var trusted bool
if trustedStr, present := features["Trusted"]; present {
trusted, err = strconv.ParseBool(trustedStr)
if err != nil {
return nil, err
}
}
conn.TrustedByRemote = trusted

uid, err := ParsePeerUID(features.Get("UID"))
if err != nil {
return nil, err
Expand Down Expand Up @@ -435,3 +454,7 @@ func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte)
func (conn *LocalConnection) extendReadDeadline() {
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
}

func (conn *LocalConnection) Untrusted() bool {
return !conn.TrustRemote || !conn.TrustedByRemote
}
15 changes: 15 additions & 0 deletions mesh/router.go
Expand Up @@ -37,6 +37,7 @@ type Config struct {
Password []byte
ConnLimit int
PeerDiscovery bool
TrustedSubnets []*net.IPNet
}

type Router struct {
Expand Down Expand Up @@ -181,3 +182,17 @@ func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameS
}
return origUpdate, newUpdate, nil
}

func (router *Router) Trusts(remote *RemoteConnection) bool {
if tcpAddr, err := net.ResolveTCPAddr("tcp4", remote.remoteTCPAddr); err == nil {
for _, trustedSubnet := range router.TrustedSubnets {
if trustedSubnet.Contains(tcpAddr.IP) {
return true
}
}
} else {
// Should not happen as remoteTCPAddr was obtained from TCPConn
log.Errorf("Unable to parse remote TCP addr: %s", err)
}
return false
}
20 changes: 19 additions & 1 deletion mesh/status.go
Expand Up @@ -2,6 +2,7 @@ package mesh

import (
"fmt"
"net"
)

type Status struct {
Expand All @@ -19,6 +20,7 @@ type Status struct {
Connections []LocalConnectionStatus
Targets []string
OverlayDiagnostics interface{}
TrustedSubnets []string
}

type PeerStatus struct {
Expand Down Expand Up @@ -69,7 +71,8 @@ func NewStatus(router *Router) *Status {
NewBroadcastRouteStatusSlice(router.Routes),
NewLocalConnectionStatusSlice(router.ConnectionMaker),
NewTargetSlice(router.ConnectionMaker),
router.Overlay.Diagnostics()}
router.Overlay.Diagnostics(),
NewTrustedSubnetsSlice(router.TrustedSubnets)}
}

func NewPeerStatusSlice(peers *Peers) []PeerStatus {
Expand Down Expand Up @@ -147,6 +150,13 @@ func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus
}
lc, _ := conn.(*LocalConnection)
info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
if lc.Router.UsingPassword() {
if lc.Untrusted() {
info = fmt.Sprintf("%-11v %v", "encrypted", info)
} else {
info = fmt.Sprintf("%-11v %v", "unencrypted", info)
}
}
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info})
}
for address, target := range cm.targets {
Expand Down Expand Up @@ -191,3 +201,11 @@ func NewTargetSlice(cm *ConnectionMaker) []string {
}
return <-resultChan
}

func NewTrustedSubnetsSlice(trustedSubnets []*net.IPNet) []string {
trustedSubnetStrs := []string{}
for _, trustedSubnet := range trustedSubnets {
trustedSubnetStrs = append(trustedSubnetStrs, trustedSubnet.String())
}
return trustedSubnetStrs
}
51 changes: 26 additions & 25 deletions prog/weaver/http.go
Expand Up @@ -27,11 +27,11 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{
}
return count
},
"upstreamServers": func(servers []string) string {
if len(servers) == 0 {
"printList": func(list []string) string {
if len(list) == 0 {
return "none"
}
return strings.Join(servers, ", ")
return strings.Join(list, ", ")
},
"printIPAMRanges": func(router weave.NetworkRouterStatus, status ipam.Status) string {
var buffer bytes.Buffer
Expand Down Expand Up @@ -138,47 +138,48 @@ func defTemplate(name string, text string) *template.Template {
}

var statusTemplate = defTemplate("status", `\
Version: {{.Version}}
Version: {{.Version}}
Service: router
Protocol: {{.Router.Protocol}} \
Service: router
Protocol: {{.Router.Protocol}} \
{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\
{{.Router.ProtocolMaxVersion}}\
{{else}}\
{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\
{{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
Name: {{.Router.Name}}({{.Router.NickName}})
Encryption: {{printState .Router.Encryption}}
PeerDiscovery: {{printState .Router.PeerDiscovery}}
Targets: {{len .Router.Targets}}
Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}}
Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections){{end}}
TrustedSubnets: {{printList .Router.TrustedSubnets}}
{{if .IPAM}}\
Service: ipam
Service: ipam
{{if .IPAM.Entries}}\
{{if allIPAMOwnersUnreachable .IPAM}}\
Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead
Status: all IP ranges owned by unreachable peers - use 'rmpeer' if they are dead
{{else if len .IPAM.PendingAllocates}}\
Status: waiting for IP range grant from peers
Status: waiting for IP range grant from peers
{{else}}\
Status: ready
Status: ready
{{end}}\
{{else if .IPAM.Paxos}}\
Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
Status: awaiting consensus (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}})
{{else}}\
Status: idle
Status: idle
{{end}}\
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
Range: {{.IPAM.Range}}
DefaultSubnet: {{.IPAM.DefaultSubnet}}
{{end}}\
{{if .DNS}}\
Service: dns
Domain: {{.DNS.Domain}}
Upstream: {{upstreamServers .DNS.Upstream}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
Service: dns
Domain: {{.DNS.Domain}}
Upstream: {{printList .DNS.Upstream}}
TTL: {{.DNS.TTL}}
Entries: {{countDNSEntries .DNS.Entries}}
{{end}}\
`)

Expand Down
30 changes: 26 additions & 4 deletions prog/weaver/main.go
Expand Up @@ -69,6 +69,7 @@ func main() {
dnsEffectiveListenAddress string
iface *net.Interface
datapathName string
trustedSubnetStr string
)

mflag.BoolVar(&justVersion, []string{"#version", "-version"}, false, "print version and exit")
Expand Down Expand Up @@ -101,6 +102,8 @@ func main() {
mflag.StringVar(&dnsEffectiveListenAddress, []string{"-dns-effective-listen-address"}, "", "address DNS will actually be listening, after Docker port mapping")
mflag.StringVar(&datapathName, []string{"-datapath"}, "", "ODP datapath name")

mflag.StringVar(&trustedSubnetStr, []string{"-trusted-subnets"}, "", "Command separated list of trusted subnets in CIDR notation")

// crude way of detecting that we probably have been started in a
// container, with `weave launch` --> suppress misleading paths in
// mflags error messages.
Expand Down Expand Up @@ -193,10 +196,7 @@ func main() {
Log.Println("Communication between peers is unencrypted.")
} else {
config.Password = []byte(password)
Log.Println("Communication between peers is encrypted.")

// fastdp doesn't support encryption
fastDPOverlay = nil
Log.Println("Communication between peers via untrusted networks is encrypted.")
}

overlays := weave.NewOverlaySwitch()
Expand Down Expand Up @@ -237,6 +237,10 @@ func main() {
networkConfig.PacketLogging = nopPacketLogging{}
}

if config.TrustedSubnets, err = parseTrustedSubnets(trustedSubnetStr); err != nil {
Log.Fatal("Unable to parse trusted subnets: ", err)
}

router := weave.NewNetworkRouter(config, networkConfig, name, nickName, overlays)
Log.Println("Our name is", router.Ourself)

Expand Down Expand Up @@ -405,6 +409,24 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint {
return quorum
}

func parseTrustedSubnets(trustedSubnetStr string) ([]*net.IPNet, error) {
trustedSubnets := []*net.IPNet{}

if trustedSubnetStr == "" {
return trustedSubnets, nil
}

for _, subnetStr := range strings.Split(trustedSubnetStr, ",") {
_, subnet, err := net.ParseCIDR(subnetStr)
if err != nil {
return nil, err
}
trustedSubnets = append(trustedSubnets, subnet)
}

return trustedSubnets, nil
}

func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) {
protocol := "tcp"
if strings.HasPrefix(httpAddr, "/") {
Expand Down
5 changes: 2 additions & 3 deletions router/fastdp.go
Expand Up @@ -515,9 +515,8 @@ type fastDatapathForwarder struct {

func (fastdp fastDatapathOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) {
if params.SessionKey != nil {
// No encryption suport in fastdp. The weaver main.go
// is responsible for ensuring this doesn't happen.
log.Fatal("Attempt to use FastDatapath with encryption")
// No encryption support in fastdp
return nil, fmt.Errorf("encryption not supported")
}

vxlanVportID := fastdp.mainVxlanVportID
Expand Down
12 changes: 10 additions & 2 deletions router/overlay_switch.go
Expand Up @@ -204,8 +204,16 @@ func (osw *OverlaySwitch) PrepareConnection(params mesh.OverlayConnectionParams)

subConn, err := overlay.PrepareConnection(params)
if err != nil {
fwd.stopFrom(0)
return nil, err
log.Infof("Unable to use %s for connection to %s(%s): %s",
overlay.name,
params.RemotePeer.Name,
params.RemotePeer.NickName,
err)
// failed to start subforwarder - record overlay name and continue
fwd.forwarders[i] = subForwarder{
overlayName: overlay.name,
}
continue
}
subFwd := subConn.(OverlayForwarder)

Expand Down
18 changes: 13 additions & 5 deletions site/features.md
Expand Up @@ -63,7 +63,9 @@ Weave automatically chooses the fastest available method to transport
data between peers. The most performant of these ('fastdp') offers
near-native throughput and latency but does not support encryption;
consequently supplying a password will cause the router to fall back
to a slower mode ('sleeve') that does.
to a slower mode ('sleeve') that does, for connections that traverse
untrusted networks (see the [security](#security) section for more
details).

Even when encryption is not in use, certain adverse network conditions
will cause this fallback to occur dynamically; in these circumstances,
Expand Down Expand Up @@ -321,10 +323,16 @@ way to generate a random password which satsifies this requirement is

< /dev/urandom tr -dc A-Za-z0-9 | head -c9 ; echo

The same password must be specified for all weave peers. Note that
supplying a password will [cause weave to fall back to a slower
method](#fast-data-path) for transporting data between
peers.
The same password must be specified for all weave peers; by default
both control and data plane traffic will then use authenticated
encryption. If some of your peers are colocated in a trusted network
(for example within the boundary of your own datacentre) you can use
the `--trusted-subnets` argument to `weave launch` to selectively
disable data plane encryption as an optimisation. Both peers must
consider the other to be in a trusted subnet for this to take place -
if they do not, weave will [fall back to a slower
method](#fast-data-path) for transporting data between peers as fast
datapath does not support encryption.

Be aware that:

Expand Down

0 comments on commit c973808

Please sign in to comment.