Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote key escrow #1096

Merged
merged 9 commits into from
Feb 23, 2017
Merged

Remote key escrow #1096

merged 9 commits into from
Feb 23, 2017

Conversation

endophage
Copy link
Contributor

Remote key escrow via a GRPC implementation of the trustmanager.Storage interface.

Still finishing it up but intend for Notary Signer to be able to act as an escrow backend with configurable APIs (so it can host either or both of: current key storage for server and/or the new GRPC trustmanager.Storage server).

By implementing the low level trustmanager.Storage interface, passwords, and therefore key encryption and decryption, still happen at the client, so compromise of the remote key storage does not equate to compromise of the escrowed keys.

Signed-off-by: David Lawrence david.lawrence@docker.com (github: endophage)

@endophage endophage force-pushed the pr_interfaces branch 13 times, most recently from 99fe973 to 59bba92 Compare February 16, 2017 16:20
@endophage endophage changed the title WIP: remote key escrow Remote key escrow Feb 16, 2017
@endophage
Copy link
Contributor Author

This is about as tested as it's going to get. The compiled .pb.go is hurting coverage but I'm not going to write tests for generated code.

@endophage endophage requested review from cyli and riyazdf February 16, 2017 21:17
@endophage endophage force-pushed the pr_interfaces branch 2 times, most recently from 8123c42 to ca60db8 Compare February 17, 2017 01:17
Copy link
Contributor

@riyazdf riyazdf left a comment

Choose a reason for hiding this comment

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

I really like how clean this is, awesome work!

Forward looking (can be a separate PR): it would be really cool to add this to our integration tests, spinning up the escrow service and making sure the same delegation and publish flow works

@@ -176,6 +180,9 @@ client: ${PREFIX}/bin/notary
binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
@echo "+ $@"

escrow: ${PREFIX}/bin/escrow
Copy link
Contributor

Choose a reason for hiding this comment

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

how do you feel about notary-escrow as a name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No strong feelings. It didn't seem necessary as "escrow" isn't quite as generic as "server". I mostly just got tired of typing notary-* on the front of everything :-P

Copy link
Contributor

Choose a reason for hiding this comment

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

Should the escrow binary also be added to the binaries and static targets?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cyli I think once we have configurable integration with the NotaryRepository that would make sense. At the moment it doesn't really plug into anything and until @n4ss's dependency injection PR is merged, there's no way to plug it in short of forked code.

"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"net"
Copy link
Contributor

Choose a reason for hiding this comment

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

non-blocking nit: did we want to do the imports formatting into separate blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is how gogland is arranging them for me :-/ I need to try breaking them up into our normal format and see what it does with new imports that get auto-added. If we're all having to constantly tweak the import list though, maybe we stop trying so hard to organize it. It looks nicer the way we've been doing it, but I think everyone also loves just having imports auto-managed.

@@ -0,0 +1,9 @@
[storage]
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: as much as I prefer TOML, do you think it's worth keeping JSON for consistency? The parsing should still work across JSON and TOML, so maybe we can include a JSON example as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd actually prefer converting everything to toml, I just didn't want to do it in this PR. I'll file an issue for it. Viper will make it little more than just rewriting the json files and updating our Dockerfiles and docker-compose files to point at the toml versions.

Copy link
Contributor

Choose a reason for hiding this comment

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

sounds good to me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
func (err ErrKeyNotFound) Error() string {
return fmt.Sprintf("signing key not found: %s", err.KeyID)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks for consolidating this 👍

// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
func (err ErrKeyNotFound) Error() string {
return fmt.Sprintf("signing key not found: %s", err.KeyID)
// KeyInfo stores the role, path, and gun for a corresponding private key ID
Copy link
Contributor

Choose a reason for hiding this comment

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

nit since you're moving things around: remove path from description

location string
}

var _ trustmanager.Storage = &RemoteStore{}
Copy link
Contributor

Choose a reason for hiding this comment

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

why is this necessary? Is this due to the nature of GRPC?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hacky go compile time check to ensure a type implements the desired interface. You'll see it around distribution a lot too. It's relatively idiomatic if unofficial.

}

// Get returns the file content found at fileName relative to the base directory
// of the file store. The path is cleaned before being made absolute to ensure
Copy link
Contributor

@riyazdf riyazdf Feb 17, 2017

Choose a reason for hiding this comment

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

GH is unfortunately collapsing many diffs and so I'm having trouble finding the cleaning logic - could you please point me to it? (or are we relying on the underlying storage?) Also would be nice to have dedicated tests for exercising this, if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These comments are just copied from the interface definition. The cleaning actually happens in the server side after passing through to the real underlying storage.

func (s *RemoteStore) ListFiles() []string {
logrus.Infof("listing files from %s", s.location)
fl, err := s.client.ListFiles(context.Background(), &google_protobuf.Empty{})
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add a test case for empty ListFiles()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The GRPC API for Go always adds an error, but ListFiles never actually errors. The only way this error case would be hit is an error at the GRPC level.

}

// Location returns a human readable indication of where the storage is located.
func (s *RemoteStore) Location() string {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can we add a test case for Location in case of any regressions?

Copy link
Contributor

@ecordell ecordell left a comment

Choose a reason for hiding this comment

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

This looks good to me for just the keystore - but what about for a full repository? I liked the interface defined in the first commit a lot.

// can't just use NewRemoteStore because it correctly sets up tls
// config and for testing purposes it's easier for the client to just
// be insecure
cc, err := grpc.Dial(
Copy link
Contributor

Choose a reason for hiding this comment

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

NewRemoteStore could swap out grpc.WithTransportCredentials for grpc.WithInsecure if the tlsconfig is nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct but it seems like InsecureSkipVerify and WithInsecure achieve the same thing so I figured it was easier to just use the well understood tls config and let users set that up however they please, rather than having 2 ways to set clients to insecure mode.

Copy link
Contributor

Choose a reason for hiding this comment

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

would it be easier to just use the certs already included in the project for testing? it's not a big deal since anyone actually running this will certainly discover problems with tls config right away, but it would be nice to have it in the test path if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed

}

// Set, Get, List, Remove, List, Get
func TestGRPCStorage(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about the error cases?

@endophage
Copy link
Contributor Author

@ecordell the full repo will be coming in follow up PRs, with something identical or at least very, very close to that interface. This was a nice self contained piece of functionality (that is also somewhat necessary for the remote full repository).

The dependency injection PR from @n4ss is also foundational work for the full remote repo.

FileName: fileName,
Data: data,
}
_, err := s.client.Set(context.Background(), sm)
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocking - should we be setting timeouts for these by default?


message StringListMsg {
repeated string FileNames = 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocking: missing newline

// ListFiles returns all known identifiers in the storage backend.
func (s *GRPCStorage) ListFiles(ctx context.Context, _ *google_protobuf.Empty) (*StringListMsg, error) {
lst := s.backend.ListFiles()
logrus.Debugf("found keys: %s", strings.Join(lst, ","))
Copy link
Contributor

Choose a reason for hiding this comment

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

If the signer is eventually going to be a remote escrow, should we actually list every key in the signer in the debug logs? Perhaps we can debug log the number?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really need an "ultra debug" mode. Listing keys has been useful but you're right that in this case it could be a lot.


// Set writes the provided data under the given identifier.
func (s *GRPCStorage) Set(ctx context.Context, msg *SetMsg) (*google_protobuf.Empty, error) {
logrus.Debugf("storing: %s", msg.FileName)
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocking: Should the other RPC calls also be logged? Or at least Remove, since both are writes?

@cyli
Copy link
Contributor

cyli commented Feb 22, 2017

Could we also add trustmanager/remoteks/*.pb.go to the codecov ignore section?

…age interface

Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
David Lawrence added 7 commits February 22, 2017 13:58
…ory for reference and in case it doesn't exist yet elsewhere)

Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
…codecov ignore, removing debug list of all keys in remote store

Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
@endophage
Copy link
Contributor Author

I think I've addressed everything here. Just rebased, tests passed locally, hopefully circle agrees.

codecov.yml Outdated
@@ -20,5 +20,6 @@ coverage:
- "tuf/testutils/*"
- "vendor/*"
- "proto/*.pb.go"
- "trustmanager/remoteks/keystore.proto"
Copy link
Contributor

@cyli cyli Feb 22, 2017

Choose a reason for hiding this comment

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

I think you may need to exclude the *.pb.go files, since the coverage will be calculated for the generated go files, not the proto files (I don't think codecov knows about protos)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh sorry, I totally crossed my wires. I meant to exclude the pb.go!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok fixed now. Must have been one of those occasions where I was thinking of something else while typing and the wrong thing came out.

Copy link
Contributor

Choose a reason for hiding this comment

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

I do that at least half the time :)

@cyli
Copy link
Contributor

cyli commented Feb 22, 2017

I've re-kicked the yubikey tests, and they passed. Codecov is being weird. :( This otherwise LGTM, modulo a codecov ignore change (which does not account for the codecov weirdness atm)

Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Copy link
Contributor

@riyazdf riyazdf left a comment

Choose a reason for hiding this comment

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

LGTM, this is very slick!

@endophage endophage merged commit 7b2ba1c into notaryproject:master Feb 23, 2017
@endophage endophage deleted the pr_interfaces branch February 23, 2017 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants