Add an additional transitional mode to get us to enforced safely

This commit is contained in:
Nate Brown 2021-04-14 20:32:43 -05:00
parent c1ed78ffc7
commit ba8646fa83
4 changed files with 115 additions and 44 deletions

View file

@ -190,37 +190,69 @@ func TestPSK(t *testing.T) {
myPskMode nebula.PskMode myPskMode nebula.PskMode
theirPskMode nebula.PskMode theirPskMode nebula.PskMode
}{ }{
// None and transitional-accepting both ways
{ {
name: "none to transitional", name: "none to transitional-accepting",
myPskMode: nebula.PskNone, myPskMode: nebula.PskNone,
theirPskMode: nebula.PskTransitional, theirPskMode: nebula.PskTransitionalAccepting,
}, },
{ {
name: "transitional to none", name: "transitional-accepting to none",
myPskMode: nebula.PskTransitional, myPskMode: nebula.PskTransitionalAccepting,
theirPskMode: nebula.PskNone, theirPskMode: nebula.PskNone,
}, },
// All transitional-accepting
{ {
name: "both transitional", name: "both transitional-accepting",
myPskMode: nebula.PskTransitional, myPskMode: nebula.PskTransitionalAccepting,
theirPskMode: nebula.PskTransitional, theirPskMode: nebula.PskTransitionalAccepting,
}, },
// transitional-accepting and transitional-sending both ways
{ {
name: "enforced to transitional", name: "transitional-accepting to transitional-sending",
myPskMode: nebula.PskEnforced, myPskMode: nebula.PskTransitionalAccepting,
theirPskMode: nebula.PskTransitional, theirPskMode: nebula.PskTransitionalSending,
}, },
{ {
name: "transitional to enforced", name: "transitional-sending to transitional-accepting",
myPskMode: nebula.PskTransitional, 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, theirPskMode: nebula.PskEnforced,
}, },
// All enforced
{ {
name: "both enforced", name: "both enforced",
myPskMode: nebula.PskEnforced, myPskMode: nebula.PskEnforced,
theirPskMode: 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 { for _, test := range tests {
@ -230,8 +262,10 @@ func TestPSK(t *testing.T) {
switch test.myPskMode { switch test.myPskMode {
case nebula.PskNone: case nebula.PskNone:
myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}} myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}}
case nebula.PskTransitional: case nebula.PskTransitionalAccepting:
myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional", "keys": []string{"this is a key"}}}} 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: case nebula.PskEnforced:
myPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key"}}}} 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 { switch test.theirPskMode {
case nebula.PskNone: case nebula.PskNone:
theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}} theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "none"}}}
case nebula.PskTransitional: case nebula.PskTransitionalAccepting:
theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "transitional", "keys": []string{"this is a key"}}}} 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: case nebula.PskEnforced:
theirPskSettings = &m{"handshakes": &m{"psk": &m{"mode": "enforced", "keys": []string{"this is a key"}}}} 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) panic(err)
} }
// If this is the stage 1 handshake packet and I am configured to enforce psk, my cert name should not appear. // If this is the stage 1 handshake packet and I am configured to send with a psk, my cert name should
// It would likely be more obvious to unmarshal the payload // 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 && h.Type == 0 && h.MessageCounter == 1 { if test.myPskMode == nebula.PskEnforced || test.myPskMode == nebula.PskTransitionalSending {
assert.NotContains(t, string(p.Data), "test me") 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 { if p.ToIp.Equal(theirUdpAddr.IP) && p.ToPort == uint16(theirUdpAddr.Port) && h.Type == 1 {

View file

@ -228,19 +228,22 @@ handshakes:
#trigger_buffer: 64 #trigger_buffer: 64
# pki can be used to mask the contents of handshakes and makes handshaking with unintended recipients more difficult # 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: psk:
# mode defines the how pre shared keys can be used in a handshake # 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. # `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. # `transitional-accepting` will send handshakes without using a psk and can receive handshakes using a psk we know about
# This is helpful for transitioning to `enforced` and should be changed to `enforced` as soon as possible. # `transitional-sending` will send handshakes using a psk but will still accept handshakes without them
# 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 # `enforced` enforces the use of a psk for all tunnels. Any node not also using `enforced` or `transitional-sending` can not handshake with us
# 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 # 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 #mode: none
# In `transitional` and `enforced` modes, the keys provided here are sent through hkdf with the intended recipients # In `transitional-accepting`, `transitional-sending` and `enforced` modes, the keys provided here are sent through
# ip used in the info section. This helps guard against handshaking with the wrong host if your static_host_map or # hkdf with the intended recipients ip used in the info section. This helps guard against handshaking with the wrong
# lighthouse(s) has incorrect information. # host if your static_host_map or lighthouse(s) has incorrect information.
# #
# Setting keys if mode is `none` has no effect. # Setting keys if mode is `none` has no effect.
# #

25
psk.go
View file

@ -24,8 +24,10 @@ func (p PskMode) String() string {
switch p { switch p {
case PskNone: case PskNone:
return "none" return "none"
case PskTransitional: case PskTransitionalAccepting:
return "transitional" return "transitional-accepting"
case PskTransitionalSending:
return "transitional-sending"
case PskEnforced: case PskEnforced:
return "enforced" return "enforced"
} }
@ -37,8 +39,10 @@ func NewPskMode(m string) (PskMode, error) {
switch m { switch m {
case "none": case "none":
return PskNone, nil return PskNone, nil
case "transitional": case "transitional-accepting":
return PskTransitional, nil return PskTransitionalAccepting, nil
case "transitional-sending":
return PskTransitionalSending, nil
case "enforced": case "enforced":
return PskEnforced, nil return PskEnforced, nil
} }
@ -46,9 +50,10 @@ func NewPskMode(m string) (PskMode, error) {
} }
const ( const (
PskNone PskMode = 0 PskNone PskMode = 0
PskTransitional PskMode = 1 PskTransitionalAccepting PskMode = 1
PskEnforced PskMode = 2 PskTransitionalSending PskMode = 2
PskEnforced PskMode = 3
) )
type Psk struct { 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. // 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 // If we are transitional or not using psks, an empty byte slice is returned
func (p *Psk) MakeFor(vpnIp iputil.VpnIp) ([]byte, error) { func (p *Psk) MakeFor(vpnIp iputil.VpnIp) ([]byte, error) {
if p.mode != PskEnforced { if p.mode == PskNone || p.mode == PskTransitionalAccepting {
return []byte{}, nil return []byte{}, nil
} }
@ -129,7 +134,7 @@ func (p *Psk) cachePsks(myVpnIp iputil.VpnIp, keys []string) error {
p.Cache = [][]byte{} p.Cache = [][]byte{}
if p.mode == PskTransitional { if p.mode == PskTransitionalAccepting || p.mode == PskTransitionalSending {
// We are transitional, we accept empty psks // We are transitional, we accept empty psks
p.Cache = append(p.Cache, nil) 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 // 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 // outgoing handshake performance, MakeFor does the final expand step
func (p *Psk) preparePrimaryKey(keys []string) error { 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 // If we aren't enforcing then there is nothing to prepare
return nil return nil
} }

View file

@ -20,16 +20,16 @@ func TestNewPsk(t *testing.T) {
assert.Equal(t, []byte{}, b) assert.Equal(t, []byte{}, b)
}) })
t.Run("mode transitional", func(t *testing.T) { t.Run("mode transitional-accepting", func(t *testing.T) {
p, err := NewPsk(PskTransitional, nil, 1) p, err := NewPsk(PskTransitionalAccepting, nil, 1)
assert.Error(t, ErrNotEnoughPskKeys, err) assert.Error(t, ErrNotEnoughPskKeys, err)
p, err = NewPsk(PskTransitional, []string{"1234567"}, 1) p, err = NewPsk(PskTransitionalAccepting, []string{"1234567"}, 1)
assert.Error(t, ErrKeyTooShort) 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.NoError(t, err)
assert.Equal(t, PskTransitional, p.mode) assert.Equal(t, PskTransitionalAccepting, p.mode)
assert.Empty(t, p.key) assert.Empty(t, p.key)
assert.Len(t, p.Cache, 2) assert.Len(t, p.Cache, 2)
@ -42,6 +42,31 @@ func TestNewPsk(t *testing.T) {
assert.Equal(t, []byte{}, b) 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) { t.Run("mode enforced", func(t *testing.T) {
p, err := NewPsk(PskEnforced, nil, 1) p, err := NewPsk(PskEnforced, nil, 1)
assert.Error(t, ErrNotEnoughPskKeys, err) assert.Error(t, ErrNotEnoughPskKeys, err)