Skip to content

[encryption] NaCL/secretbox + wrapper around etcd wal + snapshot#1701

Merged
diogomonica merged 4 commits into
moby:masterfrom
cyli:manager-encryption-package
Oct 28, 2016
Merged

[encryption] NaCL/secretbox + wrapper around etcd wal + snapshot#1701
diogomonica merged 4 commits into
moby:masterfrom
cyli:manager-encryption-package

Conversation

@cyli
Copy link
Copy Markdown
Contributor

@cyli cyli commented Oct 27, 2016

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

@codecov-io
Copy link
Copy Markdown

codecov-io commented Oct 27, 2016

Current coverage is 55.95% (diff: 90.72%)

Merging #1701 into master will increase coverage by 0.31%

@@             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   

Sunburst

Powered by Codecov. Last update 110ffb7...6917257

@cyli cyli force-pushed the manager-encryption-package branch 2 times, most recently from 44557b3 to 69ea3f3 Compare October 27, 2016 03:55
@cyli cyli force-pushed the manager-encryption-package branch 2 times, most recently from 908352f to 928ba99 Compare October 27, 2016 04:37
Comment thread api/types.proto
enum Algorithm {
NONE = 0 [(gogoproto.enumvalue_customname) = "NotEncrypted"];
SECRETBOX_SALSA20_POLY1305 = 1 [(gogoproto.enumvalue_customname) = "NACLSecretboxSalsa20Poly1305"];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is NONE for backwards compatibility, or have we stepped away from "encrypted by default, just sometimes with the key on disk"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NONE is sometimes used for encrypting the DEK only (it may optionally be encrypted with a KEK)

@aluzzardi
Copy link
Copy Markdown
Member

Is the goal to cherry pick #1598 into a few PRs, rather than bulk merging?

@cyli
Copy link
Copy Markdown
Contributor Author

cyli commented Oct 27, 2016

@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.

Comment thread manager/encryption/nacl.go Outdated
copy(lengthed[:], key)
return NACLSecretbox{
key: lengthed,
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need a sanity check that record.Nonce has at least 24 bytes?

Comment thread manager/encryption/encryption.go Outdated
decoder = NoopCoder
}
r := api.MaybeEncryptedRecord{}
if err := proto.Unmarshal(encoded, &r); err != nil || r.Size() != len(encoded) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of the r.Size() check? Worried this could cause problems if the generated protobuf code changes slightly.

Copy link
Copy Markdown
Contributor Author

@cyli cyli Oct 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread manager/encryption/encryption.go Outdated

encryptedRecord, err := encoder.Encode(plaintext)
if err != nil {
return nil, fmt.Errorf("unable to encode data: %s", err.Error())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use errors.Wrap

Comment thread manager/encryption/encryption.go Outdated

data, err := proto.Marshal(encryptedRecord)
if err != nil {
return nil, fmt.Errorf("unable to marshal as MaybeEncryptedRecord: %s", err.Error())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use errors.Wrap

@cyli cyli force-pushed the manager-encryption-package branch 3 times, most recently from ec45097 to acf8cbe Compare October 27, 2016 23:05
@aaronlehmann
Copy link
Copy Markdown
Collaborator

LGTM

Copy link
Copy Markdown
Contributor

@diogomonica diogomonica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread manager/encryption/encryption.go Outdated

// This package defines the interfaces and encryption package

const humanReadablePrefix = "SWARMMKEY-v1-"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be SWMKEY-1-

Comment thread manager/encryption/encryption.go Outdated

func (n noopCoder) Decode(e api.MaybeEncryptedRecord) ([]byte, error) {
if e.Algorithm != n.Algorithm() {
return nil, fmt.Errorf("not an unencrypted record")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double negative

Comment thread manager/encryption/encryption.go Outdated

// Encode turns a slice of bytes into a serialized MaybeEncryptedRecord slice of bytes
func Encode(plaintext []byte, encoder Encoder) ([]byte, error) {
if encoder == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just error if it's nil now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay to panic...

Comment thread manager/encryption/encryption.go Outdated
// using this package
func GenerateSecretKey() []byte {
secretData := make([]byte, naclSecretboxKeySize)
if _, err := rand.Read(secretData); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread manager/encryption/encryption_test.go Outdated

func TestEncodeDecode(t *testing.T) {
// not providing an encoder/decoder will just use the noop encoder/decoder
msg := []byte("hello swarmkit")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ReadFull here and Read above?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tab complete. :D Will fix.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the previous case

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, both cases need to use io.ReadFull.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, both use io.ReadFull now.

return nil, err
}

encrypted := secretbox.Seal(nil, data, &nonce, &n.key)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment on why the first nil?

Comment thread manager/encryption/nacl.go Outdated
if record.Algorithm != n.Algorithm() {
return nil, fmt.Errorf("not a NACL secretbox record")
}
if len(record.Nonce) != 24 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this size be a constant throughout the code? NONCE_SIZE

Comment thread manager/encryption/nacl_test.go Outdated
require.NoError(t, err)

coder := NewNACLSecretbox(key)
data := []byte("Hello world")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be Hello again world

@cyli cyli force-pushed the manager-encryption-package branch 2 times, most recently from 9d89f91 to 471523a Compare October 28, 2016 00:40
Comment thread manager/encryption/encryption.go Outdated
const humanReadablePrefix = "SWMKEY-1-"

// ErrCannotDecode is the type of error returned when some data cannot be decoded as plaintext
type ErrCannotDecode struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should introduce this error type if we aren't switching on it. Just use errors.Errorf.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do in the other PR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can just introduce it there, though, if that helps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you wrap in errors.Cause, in case we wrap the error.

At the point you detect the error. 🐹

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will do that in #1598 (just checking that you mean that we ok leaving the error type declaration here in this PR)

Comment thread manager/encryption/nacl.go Outdated
}

// Encode encrypts some bytes and returns an encrypted record
func (n NACLSecretbox) Encode(data []byte) (*api.MaybeEncryptedRecord, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't working with arbitrary types, I would recommend calling these Encrypt/Decrypt.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. And the corresponding interfaces would then be Encrypter/Decrypter?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea. I think this will help differentiate from Marshal/Unmarshal/Decode/Encode. 🐶

@cyli cyli force-pushed the manager-encryption-package branch 2 times, most recently from 697a978 to 4316be9 Compare October 28, 2016 02:04
cyli added 3 commits October 27, 2016 19:07
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>
Copy link
Copy Markdown
Member

@aluzzardi aluzzardi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Signed-off-by: cyli <ying.li@docker.com>
@cyli cyli force-pushed the manager-encryption-package branch from 4316be9 to 6917257 Compare October 28, 2016 02:07
@diogomonica diogomonica merged commit 137c3d1 into moby:master Oct 28, 2016
@cyli cyli deleted the manager-encryption-package branch October 28, 2016 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants