[encryption] NaCL/secretbox + wrapper around etcd wal + snapshot#1701
Conversation
Current coverage is 55.95% (diff: 90.72%)@@ master #1701 diff @@
==========================================
Files 89 93 +4
Lines 14631 14782 +151
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 8140 8271 +131
- Misses 5409 5422 +13
- Partials 1082 1089 +7
|
44557b3 to
69ea3f3
Compare
908352f to
928ba99
Compare
| enum Algorithm { | ||
| NONE = 0 [(gogoproto.enumvalue_customname) = "NotEncrypted"]; | ||
| SECRETBOX_SALSA20_POLY1305 = 1 [(gogoproto.enumvalue_customname) = "NACLSecretboxSalsa20Poly1305"]; | ||
| } |
There was a problem hiding this comment.
Is NONE for backwards compatibility, or have we stepped away from "encrypted by default, just sometimes with the key on disk"?
There was a problem hiding this comment.
The NONE is sometimes used for encrypting the DEK only (it may optionally be encrypted with a KEK)
|
Is the goal to cherry pick #1598 into a few PRs, rather than bulk merging? |
|
@aluzzardi To cherry pick it into at least one or two others before rebasing and bulk merging that one, yes. #1598 is really really huge :| It takes about 30 seconds for me to even load the diff in chrome. |
| copy(lengthed[:], key) | ||
| return NACLSecretbox{ | ||
| key: lengthed, | ||
| } |
There was a problem hiding this comment.
How about creating a NACLSecretbox above and copying directly into that? I believe that will save a copy.
| if record.Algorithm != n.Algorithm() { | ||
| return nil, fmt.Errorf("not a NACL secretbox record") | ||
| } | ||
|
|
There was a problem hiding this comment.
Does this need a sanity check that record.Nonce has at least 24 bytes?
| decoder = NoopCoder | ||
| } | ||
| r := api.MaybeEncryptedRecord{} | ||
| if err := proto.Unmarshal(encoded, &r); err != nil || r.Size() != len(encoded) { |
There was a problem hiding this comment.
What's the purpose of the r.Size() check? Worried this could cause problems if the generated protobuf code changes slightly.
There was a problem hiding this comment.
When I was testing to see if I could detect whether this was an encrypted record (for instance, loading just regular entry data that wasn't encoded as a MaybeEncryptedRecord), it sometimes partially unmarshalled without error into this record. So I just wanted to make sure it was a valid encoding.
There was a problem hiding this comment.
r.Size() calculates the size that would be needed to marshal this object. But I don't think protobuf makes any guarantees about only being able to serialize things one way. I'm not comfortable with this check because any change in the way the generated code serializes records could make it impossible to decode previously-saved records. Also, if we ever add a new field to MaybeEncryptedRecord it might cause problems. I think it's better to rely on the error returned by Unmarshal.
|
|
||
| encryptedRecord, err := encoder.Encode(plaintext) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unable to encode data: %s", err.Error()) |
|
|
||
| data, err := proto.Marshal(encryptedRecord) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unable to marshal as MaybeEncryptedRecord: %s", err.Error()) |
ec45097 to
acf8cbe
Compare
|
LGTM |
|
|
||
| // This package defines the interfaces and encryption package | ||
|
|
||
| const humanReadablePrefix = "SWARMMKEY-v1-" |
|
|
||
| func (n noopCoder) Decode(e api.MaybeEncryptedRecord) ([]byte, error) { | ||
| if e.Algorithm != n.Algorithm() { | ||
| return nil, fmt.Errorf("not an unencrypted record") |
|
|
||
| // Encode turns a slice of bytes into a serialized MaybeEncryptedRecord slice of bytes | ||
| func Encode(plaintext []byte, encoder Encoder) ([]byte, error) { | ||
| if encoder == nil { |
There was a problem hiding this comment.
Why are we allowing a nil encoder in the first place? Shouldn't we require an encoder explicitly, so lazy people (like me) can never use Encode the wrong way (have to explicitly define they want to use the nopEncoder
There was a problem hiding this comment.
I just error if it's nil now.
| // using this package | ||
| func GenerateSecretKey() []byte { | ||
| secretData := make([]byte, naclSecretboxKeySize) | ||
| if _, err := rand.Read(secretData); err != nil { |
There was a problem hiding this comment.
This is correct, but not using ReadFull gives me the ebejeebies.
Feel free to leave it like this since it's cleaner. I just really wanted to use the word ebejeebies.
|
|
||
| func TestEncodeDecode(t *testing.T) { | ||
| // not providing an encoder/decoder will just use the noop encoder/decoder | ||
| msg := []byte("hello swarmkit") |
There was a problem hiding this comment.
should be hello again swarmkit
| // Encode encrypts some bytes and returns an encrypted record | ||
| func (n NACLSecretbox) Encode(data []byte) (*api.MaybeEncryptedRecord, error) { | ||
| var nonce [24]byte | ||
| if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil { |
There was a problem hiding this comment.
why ReadFull here and Read above?
There was a problem hiding this comment.
Tab complete. :D Will fix.
There was a problem hiding this comment.
Fixed the previous case
There was a problem hiding this comment.
Just to be clear, both cases need to use io.ReadFull.
There was a problem hiding this comment.
Yep, both use io.ReadFull now.
| return nil, err | ||
| } | ||
|
|
||
| encrypted := secretbox.Seal(nil, data, &nonce, &n.key) |
There was a problem hiding this comment.
Add comment on why the first nil?
| if record.Algorithm != n.Algorithm() { | ||
| return nil, fmt.Errorf("not a NACL secretbox record") | ||
| } | ||
| if len(record.Nonce) != 24 { |
There was a problem hiding this comment.
Should this size be a constant throughout the code? NONCE_SIZE
| require.NoError(t, err) | ||
|
|
||
| coder := NewNACLSecretbox(key) | ||
| data := []byte("Hello world") |
There was a problem hiding this comment.
should be Hello again world
9d89f91 to
471523a
Compare
| const humanReadablePrefix = "SWMKEY-1-" | ||
|
|
||
| // ErrCannotDecode is the type of error returned when some data cannot be decoded as plaintext | ||
| type ErrCannotDecode struct { |
There was a problem hiding this comment.
I'm not sure if we should introduce this error type if we aren't switching on it. Just use errors.Errorf.
There was a problem hiding this comment.
I can just introduce it there, though, if that helps.
There was a problem hiding this comment.
This looks reasonable: https://github.com/docker/swarmkit/pull/1598/files#diff-8a1870ff8ffb551be79bf7069e478962R171.
Make sure you wrap in errors.Cause, in case we wrap the error.
There was a problem hiding this comment.
Make sure you wrap in errors.Cause, in case we wrap the error.
At the point you detect the error. 🐹
There was a problem hiding this comment.
Yep, will do that in #1598 (just checking that you mean that we ok leaving the error type declaration here in this PR)
| } | ||
|
|
||
| // Encode encrypts some bytes and returns an encrypted record | ||
| func (n NACLSecretbox) Encode(data []byte) (*api.MaybeEncryptedRecord, error) { |
There was a problem hiding this comment.
Since we aren't working with arbitrary types, I would recommend calling these Encrypt/Decrypt.
There was a problem hiding this comment.
Sounds good. And the corresponding interfaces would then be Encrypter/Decrypter?
There was a problem hiding this comment.
Yea. I think this will help differentiate from Marshal/Unmarshal/Decode/Encode. 🐶
697a978 to
4316be9
Compare
Signed-off-by: cyli <ying.li@docker.com>
…ecretbox encoder/decoder. Signed-off-by: cyli <ying.li@docker.com>
…nd decodes. Signed-off-by: cyli <ying.li@docker.com>
Signed-off-by: cyli <ying.li@docker.com>
4316be9 to
6917257
Compare
This is some cherry-picked code from #1598.
This vendors the nacl/secretbox package, and also adds a wrapper around the etcd wal and snapshot packages that can make use of the encrypted records and encoder/decoder.
cc @aaronlehmann @aluzzardi @stevvooe @lvh