From 44dea31d1fb3af8d8bd48febf0aa3b7a14f8127c Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Wed, 26 Jun 2019 12:28:09 -0400 Subject: [PATCH] Include a content hash of the intention for use during replication --- agent/consul/intention_endpoint.go | 3 ++ agent/consul/leader_connect.go | 9 +++-- agent/structs/intention.go | 61 ++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/agent/consul/intention_endpoint.go b/agent/consul/intention_endpoint.go index 141d1c39d..fd1a90e06 100644 --- a/agent/consul/intention_endpoint.go +++ b/agent/consul/intention_endpoint.go @@ -141,6 +141,9 @@ func (s *Intention) Apply( } } + // make sure we set the hash prior to raft application + args.Intention.SetHash(true) + // Commit resp, err := s.srv.raftApply(structs.IntentionRequestType, args) if err != nil { diff --git a/agent/consul/leader_connect.go b/agent/consul/leader_connect.go index e15431646..c990fa0f9 100644 --- a/agent/consul/leader_connect.go +++ b/agent/consul/leader_connect.go @@ -1,6 +1,7 @@ package consul import ( + "bytes" "context" "errors" "fmt" @@ -411,14 +412,14 @@ func retryLoopBackoff(stopCh <-chan struct{}, loopFn func() error, errFn func(er // diffIntentions computes the difference between the local and remote intentions // and returns lists of deletes and updates. func diffIntentions(local, remote structs.Intentions) (structs.Intentions, structs.Intentions) { - localIdx := make(map[string]uint64, len(local)) + localIdx := make(map[string][]byte, len(local)) remoteIdx := make(map[string]struct{}, len(remote)) var deletes structs.Intentions var updates structs.Intentions for _, intention := range local { - localIdx[intention.ID] = intention.ModifyIndex + localIdx[intention.ID] = intention.Hash } for _, intention := range remote { remoteIdx[intention.ID] = struct{}{} @@ -431,10 +432,10 @@ func diffIntentions(local, remote structs.Intentions) (structs.Intentions, struc } for _, intention := range remote { - existingIdx, ok := localIdx[intention.ID] + existingHash, ok := localIdx[intention.ID] if !ok { updates = append(updates, intention) - } else if existingIdx < intention.ModifyIndex { + } else if bytes.Compare(existingHash, intention.Hash) != 0 { updates = append(updates, intention) } } diff --git a/agent/structs/intention.go b/agent/structs/intention.go index 9fec00974..e928de21c 100644 --- a/agent/structs/intention.go +++ b/agent/structs/intention.go @@ -1,7 +1,9 @@ package structs import ( + "encoding/binary" "fmt" + "sort" "strconv" "strings" "time" @@ -9,6 +11,8 @@ import ( "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/go-multierror" "github.com/mitchellh/hashstructure" + + "golang.org/x/crypto/blake2b" ) const ( @@ -70,9 +74,66 @@ type Intention struct { // or modified. CreatedAt, UpdatedAt time.Time `mapstructure:"-"` + // Hash of the contents of the intention + // + // This is needed mainly for replication purposes. When replicating from + // one DC to another keeping the content Hash will allow us to detect + // content changes more efficiently than checking every single field + Hash []byte + RaftIndex } +func (x *Intention) SetHash(force bool) []byte { + if force || x.Hash == nil { + hash, err := blake2b.New256(nil) + if err != nil { + panic(err) + } + + // Any non-immutable "content" fields should be involved with the + // overall hash. The IDs are immutable which is why they aren't here. + // The raft indices are metadata similar to the hash which is why they + // aren't incorporated. CreateTime is similarly immutable + // + // The Hash is really only used for replication to determine if a token + // has changed and should be updated locally. + + // Write all the user set fields + hash.Write([]byte(x.ID)) + hash.Write([]byte(x.Description)) + hash.Write([]byte(x.SourceNS)) + hash.Write([]byte(x.SourceName)) + hash.Write([]byte(x.DestinationNS)) + hash.Write([]byte(x.DestinationName)) + hash.Write([]byte(x.SourceType)) + hash.Write([]byte(x.Action)) + hash.Write([]byte(x.DefaultAddr)) + binary.Write(hash, binary.LittleEndian, x.DefaultPort) + binary.Write(hash, binary.LittleEndian, x.Precedence) + + // hashing the metadata + var keys []string + for k := range x.Meta { + keys = append(keys, k) + } + + // keep them sorted to ensure hash stability + sort.Strings(keys) + + for _, k := range keys { + hash.Write([]byte(k)) + hash.Write([]byte(x.Meta[k])) + } + + // Finalize the hash + hashVal := hash.Sum(nil) + + x.Hash = hashVal + } + return x.Hash +} + // Validate returns an error if the intention is invalid for inserting // or updating. func (x *Intention) Validate() error {