Large KVs using chunked raft messages can fail verification
Overview of the Issue
When a large (close to 512kB) KV is applied via raft, it can fail verification and be rejected.
A large KV is applied by
KVS.Apply(), which callsserver.raftApply(): https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/consul/kvs_endpoint.go#L129raftApply()ends up callingraftApplyWithEncoder(..., structs.Encoder)structs.Encoder()encodes messages with the type in the first byte, followed by the msgpack encoded blob. https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/structs/structs.go#L3135-L3137Once encoded, the 512 kB KV will exceed
raft.SuggestedMaxDataSize.raftApplyEncodedwill "chunk" it into several messages: https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/consul/rpc.go#L999-L1004ChunkingApplygenerates araft.Logper chunk, with: a.Dataset to part of the raw message passed in. b.Extensionsset to aChunkInfodescribing the number of chunks. https://github.com/hashicorp/go-raftchunking/blob/1e61ed476ac8d41cdb2ef2ca74630becfc1afab7/api.go#L92When verification is enabled, a
verifier.NewLogStore()is constructed withisLogVerifyCheckpointas theIsCheckpointFncallback.The verifier passes the whole
raft.LogtoisLogVerifyCheckpoint: https://github.com/hashicorp/raft-wal/blob/ebffec3619e9330066b00ebd42135a68ef404421/verifier/store.go#L121isLogVerifyCheckpointonly checkslog.Data[0]to determine if the message is a checkpoint or not: https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/consul/server_log_verification.go#L28-L32
That's correct for the first chunk: the first byte of data will be a type encoded by structs.Encode().
But for subsequent chunks, data[0] will be an arbitrary byte from the KV payload! If it happens to be RaftLogVerifierCheckpoint, 41, the chunk will be parsed as a checkpoint.
https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/structs/structs.go#L91
This fails (I haven't dug further into what specifically happens to it) with:
rpc error making call: raft apply failed: unable to store logs within log store, err: "failed updating verifier state: short buffer"Reproduction Steps
On a cluster with verification enabled, writing a 512kB KV of only 41 / 0x29:
python3 -c 'import sys; sys.stdout.buffer.write(b"\x29" * (512 * 1024))' | consul kv put test -will error with:
Error! Failed writing data: Unexpected response code: 500 (rpc error making call: rpc error making call: raft apply failed: unable to store logs within log store, err: "failed updating verifier state: short buffer")Inconsistent Verification
This is particularly fun if not all servers in the cluster have verification enabled. In one case in our environment, in a 5 server cluster, we had two servers using:
raft_logstore {
backend = "boltdb"
}which doesn't enable verification.
And three servers without a backend setting. That defaults to WAL with verification enabled:
https://github.com/hashicorp/consul/blob/b0296e557a0584c933a723c94be25c35086d12d8/agent/consul/server.go#L1020-L1026
If the leader is one of the two servers without verification, it will apply the raft updates for big KVs with 41 at the right offset. But the three peers with verification will reject it, and the whole cluster becomes unusable without wiping it.
Could there be some kind of warning for clusters with inconsistent verification settings? It makes this situation much worse.
Source: hashicorp/consul