Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions api/equality/equality.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package equality

import (
"crypto/subtle"
"reflect"

"github.com/docker/swarmkit/api"
Expand All @@ -27,3 +28,40 @@ func TaskStatusesEqualStable(a, b *api.TaskStatus) bool {
copyA.Timestamp, copyB.Timestamp = nil, nil
return reflect.DeepEqual(&copyA, &copyB)
}

// RootCAEqualStable compares RootCAs, excluding join tokens, which are randomly generated
func RootCAEqualStable(a, b *api.RootCA) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}

var aRotationKey, bRotationKey []byte
if a.RootRotation != nil {
aRotationKey = a.RootRotation.CAKey
}
if b.RootRotation != nil {
bRotationKey = b.RootRotation.CAKey
}
if subtle.ConstantTimeCompare(a.CAKey, b.CAKey) != 1 || subtle.ConstantTimeCompare(aRotationKey, bRotationKey) != 1 {
return false
}

copyA, copyB := *a, *b
copyA.JoinTokens, copyB.JoinTokens = api.JoinTokens{}, api.JoinTokens{}
return reflect.DeepEqual(copyA, copyB)
}

// ExternalCAsEqualStable compares lists of external CAs and determines whether they are equal.
func ExternalCAsEqualStable(a, b []*api.ExternalCA) bool {
// because DeepEqual will treat an empty list and a nil list differently, we want to manually check this first
if len(a) == 0 && len(b) == 0 {
return true
}
// The assumption is that each individual api.ExternalCA within both lists are created from deserializing from a
// protobuf, so no special affordances are made to treat a nil map and empty map in the Options field of an
// api.ExternalCA as equivalent.
return reflect.DeepEqual(a, b)
}
100 changes: 100 additions & 0 deletions api/equality/equality_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/docker/swarmkit/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestTasksEqualStable(t *testing.T) {
Expand Down Expand Up @@ -53,3 +54,102 @@ func TestTasksEqualStable(t *testing.T) {
assert.Equal(t, TasksEqualStable(tasks[0], test.task), test.expected, test.failureText)
}
}

func TestRootCAEqualStable(t *testing.T) {
root1 := api.RootCA{
CACert: []byte("1"),
CAKey: []byte("2"),
CACertHash: "hash",
}
root2 := root1
root2.JoinTokens = api.JoinTokens{
Worker: "worker",
Manager: "manager",
}
root3 := root1
root3.RootRotation = &api.RootRotation{
CACert: []byte("3"),
CAKey: []byte("4"),
CrossSignedCACert: []byte("5"),
}

for _, v := range []struct{ a, b *api.RootCA }{
{a: nil, b: nil},
{a: &root1, b: &root1},
{a: &root1, b: &root2},
{a: &root3, b: &root3},
} {
require.True(t, RootCAEqualStable(v.a, v.b), "should be equal:\n%v\n%v\n", v.a, v.b)
}

root1Permutations := []api.RootCA{root1, root1, root1}
root3Permutations := []api.RootCA{root3, root3, root3}
for _, r := range root3Permutations {
copy := *r.RootRotation
root3.RootRotation = &copy
}
root1Permutations[0].CACert = []byte("nope")
root1Permutations[1].CAKey = []byte("nope")
root1Permutations[2].CACertHash = "nope"
root3Permutations[0].RootRotation.CACert = []byte("nope")
root3Permutations[1].RootRotation.CAKey = []byte("nope")
root3Permutations[2].RootRotation.CrossSignedCACert = []byte("nope")

for _, v := range []struct{ a, b *api.RootCA }{
{a: &root1, b: &root3},
{a: &root1, b: &root1Permutations[0]},
{a: &root1, b: &root1Permutations[1]},
{a: &root1, b: &root1Permutations[2]},
{a: &root3, b: &root3Permutations[0]},
{a: &root3, b: &root3Permutations[1]},
{a: &root3, b: &root3Permutations[2]},
} {
require.False(t, RootCAEqualStable(v.a, v.b), "should not be equal:\n%v\n%v\n", v.a, v.b)
}
}

func TestExternalCAsEqualStable(t *testing.T) {
externals := []*api.ExternalCA{
{URL: "1"},
{
URL: "1",
CACert: []byte("cacert"),
},
{
URL: "1",
CACert: []byte("cacert"),
Protocol: 1,
},
{
URL: "1",
CACert: []byte("cacert"),
Options: map[string]string{
"hello": "there",
},
},
{
URL: "1",
CACert: []byte("cacert"),
Options: map[string]string{
"hello": "world",
},
},
}
// equal
for _, v := range []struct{ a, b []*api.ExternalCA }{
{a: nil, b: []*api.ExternalCA{}},
{a: externals, b: externals},
{a: externals[0:1], b: externals[0:1]},
} {
require.True(t, ExternalCAsEqualStable(v.a, v.b), "should be equal:\n%v\n%v\n", v.a, v.b)
}
// not equal
for _, v := range []struct{ a, b []*api.ExternalCA }{
{a: nil, b: externals},
{a: externals[2:3], b: externals[3:4]},
{a: externals[2:3], b: externals[4:5]},
{a: externals[3:4], b: externals[4:5]},
} {
require.False(t, ExternalCAsEqualStable(v.a, v.b), "should not be equal:\n%v\n%v\n", v.a, v.b)
}
}
Loading