-
Notifications
You must be signed in to change notification settings - Fork 515
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
Remote key escrow #1096
Conversation
99fe973
to
59bba92
Compare
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. |
8123c42
to
ca60db8
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cmd/escrow/config.go
Outdated
"github.com/spf13/viper" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials" | ||
"net" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me
There was a problem hiding this comment.
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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for consolidating this 👍
trustmanager/interfaces.go
Outdated
// 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 |
There was a problem hiding this comment.
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
trustmanager/remoteks/client.go
Outdated
location string | ||
} | ||
|
||
var _ trustmanager.Storage = &RemoteStore{} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
trustmanager/remoteks/client.go
Outdated
} | ||
|
||
// 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
trustmanager/remoteks/client.go
Outdated
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 { |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
ca60db8
to
65ebb3d
Compare
There was a problem hiding this 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.
trustmanager/remoteks/client_test.go
Outdated
// 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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
@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. |
trustmanager/remoteks/client.go
Outdated
FileName: fileName, | ||
Data: data, | ||
} | ||
_, err := s.client.Set(context.Background(), sm) |
There was a problem hiding this comment.
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: missing newline
trustmanager/remoteks/server.go
Outdated
// 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, ",")) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
Could we also add |
0d0d206
to
26896bf
Compare
…age interface Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
…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)
26896bf
to
725b7d2
Compare
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" |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
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)
725b7d2
to
a62ba41
Compare
There was a problem hiding this 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!
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)