-
Notifications
You must be signed in to change notification settings - Fork 511
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
Notary repository lazy initialization #1105
Conversation
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
…sh time Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
client/client_test.go
Outdated
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true) | ||
} | ||
|
||
// Tnitializing a repo and republishing after should succeed |
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 typo: Initializing
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.
Will fix.
client/client_test.go
Outdated
func TestRemoteRotationNonexistentRepo(t *testing.T) { | ||
// The repo is initialized at publish time after | ||
// rotating the key. We should be denied the access | ||
// to metadata by the server when trying to retrieve it. |
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'm not sure I follow why this is the case?
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 made a mistake here, my bad. This is the current behavior which is buggy, I need to fix this. It seems like operations' order is not the right one in this code path.
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.
@endophage found the issue, we needed a fullTestServer
rather than a simpleTestServer
.
client/helpers.go
Outdated
_, err := cache.GetSized(role.String(), store.NoSizeLimit) | ||
if err != nil { | ||
if _, ok := err.(store.ErrMetaNotFound); ok { | ||
continue |
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.
is this intended? This function seems to return true if a cache exists but none of the roles have metadata in it
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, for each roles, we search if there is metadata. We keep searching for other roles only if the error we get while searching is that there is no metadata in it.
Is there a bug in the logic or implementation in your opinion?
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.
@n4ss - sorry yes, you're correct. Could we add a docstring-style comment to this function?
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.
will do!
cmd/notary/tuf.go
Outdated
@@ -142,7 +142,10 @@ func (t *tufCommander) AddToCommand(cmd *cobra.Command) { | |||
cmdReset.Flags().BoolVar(&t.resetAll, "all", false, "Reset all changes shown in the status list") | |||
cmd.AddCommand(cmdReset) | |||
|
|||
cmd.AddCommand(cmdTUFPublishTemplate.ToCommand(t.tufPublish)) | |||
cmdTUFPublish := cmdTUFPublishTemplate.ToCommand(t.tufPublish) | |||
cmdTUFPublish.Flags().StringVar(&t.rootKey, "rootkey", "", "Root key to initialize the repository with") |
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 the flag description indicate that it's only used if this is the first publish?
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'll actually remove this as we don't really need it.
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.
hm it depends on whether we want lazy initialization to support this (we'd have to inject the root key into this call to Initialize
), and it will get messy with the auto-publish flag.
I'm ok with removing this for now, since notary init
will still allow for this functionality. We can figure out the best way to handle this for lazy init on a followup PR 👍
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, I tried to draft it but there is no clean way to propagate the root key to Initialize in this code path.. we should talk about it later indeed
@@ -1031,14 +1037,14 @@ func maybeAutoPublish(cmd *cobra.Command, doPublish bool, gun data.GUN, config * | |||
return err | |||
} | |||
|
|||
cmd.Println("Auto-publishing changes to", gun) | |||
return publishAndPrintToCLI(cmd, nRepo, gun) | |||
cmd.Println("Auto-publishing changes to", nRepo.GetGUN()) |
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.
gun
is already an argument to this function. No need to create a new getter for it on NotaryRepository.
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.
ah you're right, the getter isn't needed - for some reason thought I had seen it somewhere else 😅
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.
repo.Publish()
will use repo.gun
for every operations (in bootstrapClient
in oldKeysForLegacyClientSupport
for example) and not necessarily the one provided as an argument here.
Providing a different gun
that the one inside the repository object could lead to misleading logging entry here, this is why I removed it from the prototype and used the "internal" one instead. Does it make sense?
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 don't think you need the getter because we use thegun
argument to get the repository here
nRepo, err := notaryclient.NewFileCachedNotaryRepository(
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, passRetriever, trustPin)
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 was thinking about the case where this function might be called elsewhere in the same package, we have no "contract" on the fact that the gun in the argument is the same as the one used for this repo.
It's just less error-prone if we don't allow someone to provide an unrelated gun. For now it's only used properly because we get it from this same repo but people might call it in a different way..
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 was thinking about the case where this function might be called elsewhere in the same package, we have no "contract" on the fact that the gun in the argument is the same as the one used for this repo.
If the gun passed in to the constructor of the repo, wouldn't that gun and the gun on the repo by contract of the NotaryRepository
have to be the same?
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.
Update: unless you mean the contract between the gun and publishAndPrintToCLI
?
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 mean the contract at the publishAndPrintToCLI
level only, which can be called with different values for repo.gun
and gun
.
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.
Ah ok, that makes sense.
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
client/client_test.go
Outdated
} | ||
|
||
// Initializing a repo and republishing after should succeed | ||
func TestPublishInitializedRepo(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.
I think this test case may already be covered by TestPublishBareRepo
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.
indeed, will fix.
client/client.go
Outdated
@@ -837,6 +834,29 @@ func (r *NotaryRepository) bootstrapRepo() error { | |||
return nil | |||
} | |||
|
|||
// initializeFromCache looks for cached metadata to bootstrap from |
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: Since isMetaCached
basically running the same loop as bootstrapRepo
, I wonder if we can modify bootstrapRepo
to just call Initialize
if it encounters an ErrMetaNotFound
error that is not related to a missing timestamp or snapshot.
That would eliminate the need for initializeFromCache
.
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.
Alternately, in the publish
logic, if ErrMetadataNotFound
is returned by bootstrapRepo
, we can then call Initialize
rather than return the previous ErrRepoNotInitialized
.
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.
Will fix with the second solution. Thanks that's way cleaner that way.
…scratch Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
cmd/notary/tuf.go
Outdated
@@ -142,7 +142,9 @@ func (t *tufCommander) AddToCommand(cmd *cobra.Command) { | |||
cmdReset.Flags().BoolVar(&t.resetAll, "all", false, "Reset all changes shown in the status list") | |||
cmd.AddCommand(cmdReset) | |||
|
|||
cmd.AddCommand(cmdTUFPublishTemplate.ToCommand(t.tufPublish)) |
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: I think this can stay as is now that the flag is gone
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!
if _, ok := err.(ErrRepositoryNotExist); ok { | ||
err := r.bootstrapRepo() | ||
if _, ok := err.(store.ErrMetaNotFound); ok { |
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.
Great cleanup! Could we add some debug logs for these cases?
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.
sure, good idea
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.
(fixed)
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: I'm wondering if this should be logged as Info
so that users can see it. Mainly because while trying this out, I'm seeing something like:
$ bin/notary -c cmd/notary/config.json delegation add repo2 targets/releases --all-paths fixtures/notary-server.crt
Addition of delegation role targets/releases with keys [d67a3bf8194b6cccbce29c859dc3677fedfb0a5e236d24b57c37edc202cc73dd], with paths ["" <all paths>], to repository "repo2" staged for next publish.
$ bin/notary -c cmd/notary/config.json publish repo2
Pushing changes to repo2
Enter passphrase for root key with ID f6ff23d:
Enter passphrase for new targets key with ID 4788565:
Repeat passphrase for new targets key with ID 4788565:
Passphrases do not match. Please retry.
Enter passphrase for new targets key with ID 4788565:
Repeat passphrase for new targets key with ID 4788565:
Enter passphrase for new snapshot key with ID b5c6cd5:
Repeat passphrase for new snapshot key with ID b5c6cd5:
Successfully published changes for repository repo2
And there's not a lot of difference between "passphrase for root key" and "passphrase for new targets key", etc. It may be useful for users to see a note about what's going on, instead of wondering why they're being asked for all these passphrases (since it's hard to distinguish between the output of the lazy init vs publishing an actual change).
Possibly the log message could be "No TUF data found locally or remotely - initializing repository %s for the first time" or something? (also totally cool with "from scratch", but "first time" might make it more clear to users since we document how notary generates keys when initializing repos for the first time.
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.
Very good point, I didn't realize how disturbing it might be regarding the passphrases.
Will fix!
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
Other than minor bikeshedding about a log message, this LGTM! Thanks for all your work on this! |
This came out a LOT shorter than I was anticipating! Great work. It looks good but I'm reviewing it late-ish at night so will take another look when I'm a bit more alert. |
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 looks great.
Signed-off-by: Nassim 'Nass' Eddequiouaq <eddequiouaq.nassim@gmail.com>
This work depends on the work done in #1094, allowing us to inject a
remote store
and acache
in aNotaryRepository
object.Since this PR is stacked on top of #1094 which just got merged, make sure you pull the latests changes.
We are now initializing the repository when publishing, if needed. We do this for three reasons:
The lazy initialization is done either from scratch by
NotaryRepository.Initialize()
when starting from a blank state or byNotaryRepository.bootstrapRepo()
if metadata is found on disk (example: when someone does anotary key rotate
as a first operation).Some updates have been added to the way change lists were handled, we can now inject one in a
NotaryRepository
object.