From ba8646fa835fb4c9a2649c11eaf5cf91b5b0beec Mon Sep 17 00:00:00 2001 From: Nate Brown Date: Wed, 14 Apr 2021 20:32:43 -0500 Subject: [PATCH] Add an additional transitional mode to get us to enforced safely --- e2e/handshakes_test.go | 78 +++++++++++++++++++++++++++++++----------- examples/config.yml | 21 +++++++----- psk.go | 25 ++++++++------ psk_test.go | 35 ++++++++++++++++--- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/e2e/handshakes_test.go b/e2e/handshakes_test.go index 263b240..b765a99 100644 --- a/e2e/handshakes_test.go +++ b/e2e/handshakes_test.go @@ -190,37 +190,69 @@ func TestPSK(t *testing.T) { myPskMode nebula.PskMode theirPskMode nebula.PskMode }{ + // None and transitional-accepting both ways { - name: "none to transitional", + name: "none to transitional-accepting", myPskMode: nebula.PskNone, - theirPskMode: nebula.PskTransitional, + theirPskMode: nebula.PskTransitionalAccepting, }, { - name: "transitional to none", - myPskMode: nebula.PskTransitional, + name: "transitional-accepting to none", + myPskMode: nebula.PskTransitionalAccepting, theirPskMode: nebula.PskNone, }, + + // All transitional-accepting { - name: "both transitional", - myPskMode: nebula.PskTransitional, - theirPskMode: nebula.PskTransitional, + name: "both transitional-accepting", + myPskMode: nebula.PskTransitionalAccepting, + theirPskMode: nebula.PskTransitionalAccepting, }, + // transitional-accepting and transitional-sending both ways { - name: "enforced to transitional", - myPskMode: nebula.PskEnforced, - theirPskMode: nebula.PskTransitional, + name: "transitional-accepting to transitional-sending", + myPskMode: nebula.PskTransitionalAccepting, + theirPskMode: nebula.PskTransitionalSending, }, { - name: "transitional to enforced", - myPskMode: nebula.PskTransitional, + name: "transitional-sending to transitional-accepting", + myPskMode: nebula.PskTransitionalSending, + theirPskMode: nebula.PskTransitionalAccepting, + }, + + // All transitional-sending + { + name: "transitional-sending to transitional-sending", + myPskMode: nebula.PskTransitionalSending, + theirPskMode: nebula.PskTransitionalSending, + }, + + // enforced and transitional-sending both ways + { + name: "enforced to transitional-sending", + myPskMode: nebula.PskEnforced, + theirPskMode: nebula.PskTransitionalSending, + }, + { + name: "transitional-sending to enforced", + myPskMode: nebula.PskTransitionalSending, theirPskMode: nebula.PskEnforced, }, + + // All enforced { name: "both enforced", myPskMode: nebula.PskEnforced, theirPskMode: nebula.PskEnforced, }, + + // Enforced can technically handshake with a traditional-accepting but it is bad to be in this state + { + name: "enforced to traditional-accepting", + myPskMode: nebula.PskEnforced, + theirPskMode: nebula.PskTransitionalAccepting, + }, } for _, test := range tests { @@ -230,8 +262,10 @@ func TestPSK(t *testing.T) { switch test.myPskMode { case nebula.PskNone: myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}} - case nebula.PskTransitional: - myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional", "keys": []string{"this is a key"}}}} + case nebula.PskTransitionalAccepting: + myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional-accepting", "keys": []string{"this is a key"}}}} + case nebula.PskTransitionalSending: + myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional-sending", "keys": []string{"this is a key"}}}} case nebula.PskEnforced: myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key"}}}} } @@ -239,8 +273,10 @@ func TestPSK(t *testing.T) { switch test.theirPskMode { case nebula.PskNone: theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}} - case nebula.PskTransitional: - theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional", "keys": []string{"this is a key"}}}} + case nebula.PskTransitionalAccepting: + theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional-accepting", "keys": []string{"this is a key"}}}} + case nebula.PskTransitionalSending: + theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional-sending", "keys": []string{"this is a key"}}}} case nebula.PskEnforced: theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key"}}}} } @@ -265,10 +301,12 @@ func TestPSK(t *testing.T) { panic(err) } - // If this is the stage 1 handshake packet and I am configured to enforce psk, my cert name should not appear. - // It would likely be more obvious to unmarshal the payload - if test.myPskMode == nebula.PskEnforced && h.Type == 0 && h.MessageCounter == 1 { - assert.NotContains(t, string(p.Data), "test me") + // If this is the stage 1 handshake packet and I am configured to send with a psk, my cert name should + // not appear. It would likely be more obvious to unmarshal the payload and check but this works fine for now + if test.myPskMode == nebula.PskEnforced || test.myPskMode == nebula.PskTransitionalSending { + if h.Type == 0 && h.MessageCounter == 1 { + assert.NotContains(t, string(p.Data), "test me") + } } if p.ToIp.Equal(theirUdpAddr.IP) && p.ToPort == uint16(theirUdpAddr.Port) && h.Type == 1 { diff --git a/examples/config.yml b/examples/config.yml index 77f6ea1..733973b 100644 --- a/examples/config.yml +++ b/examples/config.yml @@ -228,19 +228,22 @@ handshakes: #trigger_buffer: 64 # pki can be used to mask the contents of handshakes and makes handshaking with unintended recipients more difficult + # all settings respond to a reload psk: # mode defines the how pre shared keys can be used in a handshake - # `none` (the default) does not send or receive using a psk. Ideally `enforced` is used. - # `transitional` can receive handshakes using a psk that we know about, but we will not send any handshakes using a psk. - # This is helpful for transitioning to `enforced` and should be changed to `enforced` as soon as possible. - # Move every node in your mesh to `transitional` then you can move every node in your mesh to `enforced` without having to stop the world - # This assumes `keys` is the same on every node in your mesh - # `enforced` enforces the use of a psk for all tunnels. Any node not also using `enforced` or `transitional` will not be able to handshake with us + # `none` (the default) does not send or receive using a psk. Ideally `enforced` is used + # `transitional-accepting` will send handshakes without using a psk and can receive handshakes using a psk we know about + # `transitional-sending` will send handshakes using a psk but will still accept handshakes without them + # `enforced` enforces the use of a psk for all tunnels. Any node not also using `enforced` or `transitional-sending` can not handshake with us + # + # When moving from `none` to `enforced` you will want to change every node in the mesh to `transitional-accepting` and reload + # then move every node to `transitional-sending` then reload, and finally `enforced` then reload. This allows you to + # avoid stopping the world to use psk. You must ensure at `transitional-accepting` that all nodes have the same psks. #mode: none - # In `transitional` and `enforced` modes, the keys provided here are sent through hkdf with the intended recipients - # ip used in the info section. This helps guard against handshaking with the wrong host if your static_host_map or - # lighthouse(s) has incorrect information. + # In `transitional-accepting`, `transitional-sending` and `enforced` modes, the keys provided here are sent through + # hkdf with the intended recipients ip used in the info section. This helps guard against handshaking with the wrong + # host if your static_host_map or lighthouse(s) has incorrect information. # # Setting keys if mode is `none` has no effect. # diff --git a/psk.go b/psk.go index cd61236..8403a07 100644 --- a/psk.go +++ b/psk.go @@ -24,8 +24,10 @@ func (p PskMode) String() string { switch p { case PskNone: return "none" - case PskTransitional: - return "transitional" + case PskTransitionalAccepting: + return "transitional-accepting" + case PskTransitionalSending: + return "transitional-sending" case PskEnforced: return "enforced" } @@ -37,8 +39,10 @@ func NewPskMode(m string) (PskMode, error) { switch m { case "none": return PskNone, nil - case "transitional": - return PskTransitional, nil + case "transitional-accepting": + return PskTransitionalAccepting, nil + case "transitional-sending": + return PskTransitionalSending, nil case "enforced": return PskEnforced, nil } @@ -46,9 +50,10 @@ func NewPskMode(m string) (PskMode, error) { } const ( - PskNone PskMode = 0 - PskTransitional PskMode = 1 - PskEnforced PskMode = 2 + PskNone PskMode = 0 + PskTransitionalAccepting PskMode = 1 + PskTransitionalSending PskMode = 2 + PskEnforced PskMode = 3 ) type Psk struct { @@ -102,7 +107,7 @@ func NewPsk(mode PskMode, keys []string, myVpnIp iputil.VpnIp) (*Psk, error) { // mixing in the intended recipients vpn ip and the result is returned. // If we are transitional or not using psks, an empty byte slice is returned func (p *Psk) MakeFor(vpnIp iputil.VpnIp) ([]byte, error) { - if p.mode != PskEnforced { + if p.mode == PskNone || p.mode == PskTransitionalAccepting { return []byte{}, nil } @@ -129,7 +134,7 @@ func (p *Psk) cachePsks(myVpnIp iputil.VpnIp, keys []string) error { p.Cache = [][]byte{} - if p.mode == PskTransitional { + if p.mode == PskTransitionalAccepting || p.mode == PskTransitionalSending { // We are transitional, we accept empty psks p.Cache = append(p.Cache, nil) } @@ -150,7 +155,7 @@ func (p *Psk) cachePsks(myVpnIp iputil.VpnIp, keys []string) error { // preparePrimaryKey if we are in enforced mode, will do an hkdf extract on the first key to benefit // outgoing handshake performance, MakeFor does the final expand step func (p *Psk) preparePrimaryKey(keys []string) error { - if p.mode != PskEnforced { + if p.mode == PskNone || p.mode == PskTransitionalAccepting { // If we aren't enforcing then there is nothing to prepare return nil } diff --git a/psk_test.go b/psk_test.go index 88b7f27..cfd66eb 100644 --- a/psk_test.go +++ b/psk_test.go @@ -20,16 +20,16 @@ func TestNewPsk(t *testing.T) { assert.Equal(t, []byte{}, b) }) - t.Run("mode transitional", func(t *testing.T) { - p, err := NewPsk(PskTransitional, nil, 1) + t.Run("mode transitional-accepting", func(t *testing.T) { + p, err := NewPsk(PskTransitionalAccepting, nil, 1) assert.Error(t, ErrNotEnoughPskKeys, err) - p, err = NewPsk(PskTransitional, []string{"1234567"}, 1) + p, err = NewPsk(PskTransitionalAccepting, []string{"1234567"}, 1) assert.Error(t, ErrKeyTooShort) - p, err = NewPsk(PskTransitional, []string{"hi there friends"}, 1) + p, err = NewPsk(PskTransitionalAccepting, []string{"hi there friends"}, 1) assert.NoError(t, err) - assert.Equal(t, PskTransitional, p.mode) + assert.Equal(t, PskTransitionalAccepting, p.mode) assert.Empty(t, p.key) assert.Len(t, p.Cache, 2) @@ -42,6 +42,31 @@ func TestNewPsk(t *testing.T) { assert.Equal(t, []byte{}, b) }) + t.Run("mode transitional-sending", func(t *testing.T) { + p, err := NewPsk(PskTransitionalSending, nil, 1) + assert.Error(t, ErrNotEnoughPskKeys, err) + + p, err = NewPsk(PskTransitionalSending, []string{"1234567"}, 1) + assert.Error(t, ErrKeyTooShort) + + p, err = NewPsk(PskTransitionalSending, []string{"hi there friends"}, 1) + assert.NoError(t, err) + assert.Equal(t, PskTransitionalSending, p.mode) + + expectedKey := []byte{0x9c, 0x67, 0xab, 0x58, 0x79, 0x5c, 0x8a, 0xf0, 0xaa, 0xf0, 0x4c, 0x6c, 0x9a, 0x42, 0x6b, 0xe, 0xe2, 0x94, 0xb1, 0x0, 0x28, 0x1c, 0xdc, 0x88, 0x44, 0x35, 0x3f, 0xb7, 0xd5, 0x9, 0xc0, 0xda} + assert.Equal(t, expectedKey, p.key) + + assert.Len(t, p.Cache, 2) + assert.Nil(t, p.Cache[0]) + + expectedCache := []byte{146, 120, 135, 31, 158, 102, 45, 189, 128, 190, 37, 101, 58, 254, 6, 166, 91, 209, 148, 131, 27, 193, 24, 25, 170, 65, 130, 189, 7, 179, 255, 17} + assert.Equal(t, expectedCache, p.Cache[1]) + + expectedPsk := []byte{0xd9, 0x16, 0xa3, 0x66, 0x6a, 0x20, 0x26, 0xcf, 0x5d, 0x93, 0xad, 0xa3, 0x88, 0x2d, 0x57, 0xac, 0x9b, 0xc3, 0x5a, 0xb7, 0x8f, 0x6, 0x71, 0xc4, 0x3e, 0x5, 0x9e, 0xbc, 0x4e, 0xc8, 0x24, 0x17} + b, err := p.MakeFor(0) + assert.Equal(t, expectedPsk, b) + }) + t.Run("mode enforced", func(t *testing.T) { p, err := NewPsk(PskEnforced, nil, 1) assert.Error(t, ErrNotEnoughPskKeys, err)