notaryproject / notary

Notary is a project that allows anyone to have trust over arbitrary collections of data
Apache License 2.0
3.24k stars 511 forks source link

Implement Threshold Signing mechanisms on the client/server #841

Open cyli opened 8 years ago

cyli commented 8 years ago

Although we validate thresholds right now, currently there is no way to set a threshold higher than 1, because we had no good user story for how this would work.

This has been discussed over email with @gtank and @ecordell, and @ecordell has kindly opened https://github.com/theupdateframework/tuf/issues/339 as a result.

Posting the relevant parts of this discussion here for everyone to review.

(from @endophage:) There are a few UX questions we need to answer with any implementation of thresholds, and are largely what have encouraged us to implement an alternate mechanism:

cyli commented 8 years ago

(from @ecordell:)

The approach we've been pondering is allowing partially signed, and thus "invalid" metadata to be pushed to the TUF server. I should add that we have some patches to Notary playing with these ideas (haven't fleshed out that test suite yet - sorry!) With that in mind I think we have decent answers to the UX questions you posed.

how do I as a signer (or "witness"), know which versions are available and which versions I can/should/need to sign?

If you request the metadata, it's either valid (meets threshold/keys) or invalid (doesn't meet threshold). If it's not valid and you're a "normal" user or a deployment system, you stop - the TUF repo is currently in an update process. There could be a mechanism to pull the latest valid metadata so that users don't have to be blocked (though some may wish to be in this case) or we could have explicitly staged metadata.

If the metadata is invalid and you're a "witness," you would pull the image and, if it meets whatever requirements you're verifying, you would notary witness with your key, and push that metadata back up to the server.

The server needs to decide when to allow updates to the metadata. We don't have any problems if we only allow updates that are signed with the previous metadata's threshold and keys. In the case of uploading "invalid" (partially signed) metadata, we would need to allow metadata that doesn't meet the threshold, but the keys must have previously been specified. This makes some operations involve more steps (e.g. lowering a threshold and actually reducing the number of signatures on the metadata) but simplifies the model (e.g. no ACL is needed).

how do I distinguish pending versions to sign, as I may not want to witness all pending versions?

In our scenario, there is only ever one pending/staged version. It's possible two clients could pull the same staged metadata, sign it, and attempt to push it up, and there would be a race to see who gets to update it. This isn't a big deal though, the loser simply needs to download the newer metadata and sign again.

how do we prevent things from being deleted by accident: i.e. assuming threshold > 1, targets v1 is empty and signed by necessary threshold. User A generates v2, adds target "a", and pushes that partially signed targets file to server. User B wants to add target "b". Does he pull the untrusted (lacking threshold signatures) v2, does he pull v1 and create a conflicting v2, or does he pull v1 in the knowledge there is a v2 and he should create v3 but without the as yet untrusted "a" target? Alternatively do we block User B from pushing until v2 is finalized?

I think the goal of any solution should be to prevent the forking you describe. Anyone with the ability to push new metadata has at least one of the private keys that are specified by the previous version of the metadata; because of this I think it's reasonable to have a first-come first-serve mechanism for new versions, perhaps with a separate, explicit option for overriding the existing pending metadata. The common case is that you're trying to successfully coordinate with other key-holders and probably don't want to overwrite their staged metadata, but there are times where that may be what you want to do ("ugh, the intern published bad metadata and left it there").

In the case of a compromised key there's the risk that someone could spam the metadata endpoint and put you in an losing race to publish with a specific threshold. A reasonable rate limit could be enough to fix that, though (e.g. pending metadata cannot be usurped more than once an hour).

To be a little more explicit re: your example, User B would attempt to publish his version of v2 and be informed that there's an existing staged v2 version, and that they should either pull and witness, or, if they're really sure they want to override it, they can go ahead and push.

how do we handle a threshold change that makes a partially signed thing become valid (i.e. it had threshold 2 but 1 sig, and the threshold is lowered to 1)?

In this case, the new metadata with threshold of 1 would still need to be signed with 2 signatures that are valid against the old metadata. Once it's been lowered to 1, another version can be published with a single signature if that's what's desired. This is what I meant earlier when I said that this makes some operations require more steps than what you might initially expect.

I think that's pretty much where we are right now. One thing that I've been thinking more lately is that we may want to make the "staged" metadata explicit, e.g. root.staged.json. I think that might make some things simpler, and it would also prevent normal users from ever seeing "invalid metadata" errors.

Threshold signing is complex enough from a user perspective that it will require excellent UI communication, and I think there's a lot of room to collaborate on how that messaging works.

cyli commented 8 years ago

In this case, the new metadata with threshold of 1 would still need to be signed with 2 signatures that are valid against the old metadata. Once it's been lowered to 1, another version can be published with a single signature if that's what's desired. This is what I meant earlier when I said that this makes some operations require more steps than what you might initially expect.

This would only be for the case where we're changing the threshold for the root role? If the targets role threshold were 2, for instance, and there's a partially signed target.json in the queue, and someone comes by and updates the root.json only to change the target role threshold to 1, that partially signed target.json is suddenly valid. (assuming the server generates snapshots and timestamps), otherwise the snapshot and target versions of the partially-signed repo metadata will no longer be valid.

Speaking of, are you planning to stage a whole partially-sigend repo? And if so, it seems like someone make a push that will completely invalidate that repo, as in the above case?

I think that's pretty much where we are right now. One thing that I've been thinking more lately is that we may want to make the "staged" metadata explicit, e.g. root.staged.json. I think that might make some things simpler, and it would also prevent normal users from ever seeing "invalid metadata" errors.

👍

ecordell commented 8 years ago

If the targets role threshold were 2, for instance, and there's a partially signed target.json in the queue, and someone comes by and updates the root.json only to change the target role threshold to 1, that partially signed target.json is suddenly valid.

That's absolutely right; I just think that's an intentional property of TUF - root key owners can wreak havoc on lower data if they choose. Someone updating the root such that a previously invalid targets becomes valid is essentially the root key holders blessing that targets file.

If it seems like that's a problem or could be a common source of errors, this could probably be solved with messaging, e.g. "If you proceed with this change, the following invalid files will become valid: ... "

Speaking of, are you planning to stage a whole partially-sigend repo? And if so, it seems like someone make a push that will completely invalidate that repo, as in the above case?

I think metadata could be staged on a per-file basis but would like to think about it some more. I believe that storing staged data separately and only promoting when it's valid makes sense.

ecordell commented 8 years ago

I should add that the description above is somewhat different from https://github.com/theupdateframework/tuf/issues/339

cyli commented 8 years ago

That's absolutely right; I just think that's an intentional property of TUF - root key owners can wreak havoc on lower data if they choose. Someone updating the root such that a previously invalid targets becomes valid is essentially the root key holders blessing that targets file.

So the logic would be:

  1. If someone 'witnesses' one or more staged files, and updates the staged files with their signatures, we attempt to do a full version update of the whole repo. If it fails, we just add their signatures to the existing files in the staged area.
  2. If someone pushes a new version of the TUF repo that is valid, we apply that first and then again attempt to do a full version update of the whole repo using the staged files. If it fails, we leave the staged files as is.

?

I think metadata could be staged on a per-file basis but would like to think about it some more. I believe that storing staged data separately and only promoting when it's valid makes sense.

This makes sense if the server signs for the timestamp and snapshot, but we do allow the user to do the snapshot signing themselves.

So if we allow threshold signing for repositories where the server does not manage snapshot signing, I think collections of metadata would have to be staged or otherwise treated as a single unit.

ecordell commented 8 years ago

Your descriptions for 1 & 2 seem spot on to me!

if we allow threshold signing for repositories where the server does not manage snapshot signing, I think collections of metadata would have to be staged or otherwise treated as a single unit.

I think the staged metadata only needs to be treated as a single unit during a "promotion" from staging -> production. Individual files could still be staged at any time. User-managed snapshot files could be exactly the same as the case for server managed snapshot, but there would be one additional "witness" step for the snapshot (could even generate the snapshot with no signatures on the server, and require snapshot key holders to witness it with the same mechanism as other metadata). Do you see any problems with doing it that way?

cyli commented 8 years ago

I think the staged metadata only needs to be treated as a single unit during a "promotion" from staging -> production. Individual files could still be staged at any time. Individual files could still be staged at any time. User-managed snapshot files could be exactly the same as the case for server managed snapshot, but there would be one additional "witness" step for the snapshot (could even generate the snapshot with no signatures on the server, and require snapshot key holders to witness it with the same mechanism as other metadata). Do you see any problems with doing it that way?

Hmm, sorry I think I am just not being clear about terminology. Agreed that metadata needs to be treated as a single unit during a promotion.

What do you mean by "individual files could still be staged at any time"? Do you mean that an individual file can be uploaded to the staging area to be witnessed, where it will be treated as part of a single set of staged metadata files that will all be promoted together?

Or do you mean that the server can have any number of individual and sets of staged metadata files, and only at promotion time does the user get to pick which individual files belong to a set to be promoted?

ecordell commented 8 years ago

What do you mean by "individual files could still be staged at any time"?

Simply that if I, as a key holder, want to stage some metadata, I can stage files as separate operations. I.e., I can upload a staged targets.json without creating an entire staged metadata collection. A user-managed snapshot is the only unusual case: we either need to generate snapshots on the server and offer them up for witnessing by snapshot key owners, or we need to have a separate snapshotting mechanism.

Manual snapshotting is tricky if there are multiple combinations of valid staged files at the same time - that may require combinatorially generating valid snapshots or a tool (notary snapshot?) that permits a snapshot key holder to pick which staged files they want to snapshot. I actually lean towards the combinatorial approach (e.g. "here are the valid combinations of the staged files, pick one: ...") for usability.

Do you mean that an individual file can be uploaded to the staging area to be witnessed, where it will be treated as part of a single set of staged metadata files that will all be promoted together? Or do you mean that the server can have any number of individual and sets of staged metadata files, and only at promotion time does the user get to pick which individual files belong to a set to be promoted?

I don't think those two are mutually exclusive. You can have, for example, multiple staged versions of a new targets.json file alongside multiple staged versions of a root.json file. (see: https://github.com/theupdateframework/tuf/issues/339#issuecomment-232709508). Which one wins is determined by which one is witnessed first (in the case of server snapshot) or what the snapshot key holder decides (in the case of user managed).

Is that ringing true or am I trivializing these UX issues? Would it help to actually propose what a UI for this would be?

cyli commented 8 years ago

Again, really sorry for not following up on this and #835 for a while. I think yes, proposing a UI might be good, and maybe starting with a simplistic model would be good?

Also, on a related note, not sure if you saw, but @endophage recently worked on the witness command (#875) to re-validate a delegation that was invalidated by removing one of the valid signing keys (if someone has left the team, for instance), which doesn't implement threshold signing, but does update the client download flow to download slightly invalid (insufficient signature) metadata, so long as the checksum is in the snapshot. Not sure if that would be of use for the threshold signing/staging case?

ecordell commented 8 years ago

so long as the checksum is in the snapshot

I saw #875 and I agree, I think that's the same mechanism that should be used for threshold signing. I'm not sure about requiring the checksum to be in the snapshot though, unless we do combinatorial signing as mentioned above.

Just to keep this discussion moving, here's a spitballed UI for witnessing. For now we'll assume a server managed snapshot to make this simpler.

# set up a repo with a delegated targets role w/ 2 keys and threshold of 2
# only sign with 1 key
$ notary init tuf/test
$ notary key import keys/onlineA.key --role targets/online
$ notary delegation add tuf/test targets/online keys/onlineA.crt keys/onlineB.crt --all-paths
$ notary add tuf/test v2 file.txt --roles="targets/online"
$ notary publish tuf/test
# currently these two commands exist only in a local fork
$ notary role threshold tuf/test targets/online 2
$ notary publish tuf/test --partial

# on another client
$ notary key import keys/onlineB.key --role targets/online
$ notary witness tuf/test

tuf/test has the following partially signed metadata matching your keys

 1. targets/online 
      signed by:
        targets/onlineA
      requires 1 more signature from:
        targets/onlineB  (yours)

to witness this metadata, run 

  notary witness tuf/test targets/online --info

and inspect the data for correctness. If it is, sign by running:

  notary witness tuf/test targets/online --key targets/onlineB 

$ notary witness tuf/test targets/online --info

  this is a delegated target role with the following data:

    v2: file.txt

$ cat file.txt

  contents looking good

$ notary witness tuf/test targets/online --key targets/onlineB

  info: tuf/test can be published without partial

$ notary publish tuf/test

So this is the happiest and simplest of paths, but it's a place to start. I think in most places it seems straightforward to extend for other scenarios (multiple staged versions of the same metadata, multiple different files that can be signed, etc).

I think the user-managed snapshot key will be the only thing that requires a little more thought, though right now I'm thinking it could just be something like:

# ...
$ notary witness tuf/test targets/online --key targets/onlineB

  info: tuf/test must be published with partial

$ notary publish tuf/test --partial

# snapshot key signer client

$ notary witness tuf/test

tuf/test has the following partially signed metadata matching your keys

 1. snapshot
      requires 1 more signature from:
        snapshot  (yours)

to witness this metadata, run 

  notary witness tuf/test snapshot --info

and inspect the data for correctness. If it is, sign by running:

  notary witness tuf/test snapshot --key snapshot

Again, multiple files (in the snapshot case, multiple unsigned generated snapshot files of compatible metadata options) would be listed and then a user would decide which is correct.

Any thoughts?

endophage commented 8 years ago

Hey Evan,

875 was just to specifically cover the case of delegations that have become invalid due to removed keys (hence why the data is also expected to still be in the snapshot, at least under our current implementation models). Prior to merging it, delegations would become permanently unusable if all valid keys were removed. However, I figured the witness command could be expanded to cover all cases of simply signing (in contrast to applying other updates to) any piece of TUF metadata, in the general style you’ve laid out in your examples.

At this point I don’t want to make any specific comments on the commands you’ve proposed, I generally think they’re aligned with our thoughts and we can bike shed over the specifics later. I do think we could present a drastically better UX by making the commands interactive, rather than having the user run multiple, only slightly different commands.

Thanks for your ongoing work on this and apologies for our slow responses!

ecordell commented 8 years ago

Great, it sounds like we're on the same page and ready to get a PR and some bikeshedding going!

I'm currently working on some notary changes that have already been discussed and validated:

I'm largely done with the first two (PRs coming soon), and after sufficient review will begin on the latter.

endophage commented 8 years ago

Sounds good! Have you considered the timescale for switching the root key rotation behaviour? We probably need some duration of overlap, where we continue to try and sign with all the old keys, while also being able to request multiple root.json files as a signer to accept a rotation. If we don't do this, all current versions of the docker integration will break.

I think the current support timeframe is the latest docker release and the 2 previous releases (not including patch releases, so current support is 1.10.x up to 1.12.x), which is ~6 months. We would need to get the consumer side into docker then essentially wait until all legacy versions had fallen out of support, before making your proposed publishing change the default.

There might be some optimizations we can make to phase out the presence of the old root roles in root.json more rapidly, like having the publisher also request old versions of the root.json to find previous root keys they should continue signing with. If we could resolve the "canonical" key ID split, where x509 formatted public keys essentially have 2 IDs, we could potentially do something as simple as requiring all key IDs that signed the previous root also sign the current one, with some CLI syntax to allow existing key IDs to be excluded from that requirement.

ecordell commented 8 years ago

Have you considered the timescale for switching the root key rotation behaviour? We probably need some duration of overlap, where we continue to try and sign with all the old keys, while also being able to request multiple root.json files as a signer to accept a rotation.

I think this is addressed simply by doing a phased deployment that makes sense for your timeline. New clients can be rolled out immediately (because the client behavior doesn't change w.r.t old servers), and after an adequate period, the server can be rolled out as well.

The good news is that current servers are already storing past versions, so the transition should be fairly easy. See #942 for the implementation.

endophage commented 8 years ago

I wasn't actually thinking of client-server problems, I'm confident we can handle those. I was thinking of client-client problems. One user pushes with a new client, can an older client still successfully accept a root rotation in all cases?

ecordell commented 8 years ago

One user pushes with a new client, can an older client still successfully accept a root rotation in all cases?

I will take some time to verify the current behavior. Based on the tests, I would say that a client will accept a root rotation. The current test suite doesn't actually verify the "signed by all previous roots" property (as I assumed), it actually just verifies "signed by immediately previous root." This could be a good thing for forwards-compatibility.

A client using TOFU would just need to delete their root.json to fix any problem, were there any.

endophage commented 8 years ago

I've always looked at the existing tests a little more generically: they test that the new root is signed with some previous set of keys I already knew about. The other factors, version numbers, presence or not of additional signatures, are incidental.

ecordell commented 8 years ago

I've verified this in a test:

// A valid root role is signed by the current root role keys and the previous root role keys
func TestRootRoleInvariant(t *testing.T) {
    // start with a repo with a root with 2 keys, optionally signing 1
    _, serverSwizzler := newServerSwizzler(t)
    ts := readOnlyServer(t, serverSwizzler.MetadataCache, http.StatusNotFound, "docker.com/notary")
    defer ts.Close()

    repo := newBlankRepo(t, ts.URL)
    defer os.RemoveAll(repo.baseDir)

    // --- setup so that the root starts with a role with 1 keys, and threshold of 1
    rootBytes, err := serverSwizzler.MetadataCache.GetSized(data.CanonicalRootRole, store.NoSizeLimit)
    require.NoError(t, err)
    signedRoot := data.SignedRoot{}
    require.NoError(t, json.Unmarshal(rootBytes, &signedRoot))

    // save the old role to prove that it is not needed for client updates
    oldVersion := fmt.Sprintf("%v.%v", data.CanonicalRootRole, signedRoot.Signed.Version)
    signedRoot.Signed.Roles[oldVersion] = &data.RootRole{
        Threshold: 1,
        KeyIDs:    signedRoot.Signed.Roles[data.CanonicalRootRole].KeyIDs,
    }

    threeKeys := make([]data.PublicKey, 3)
    keyIDs := make([]string, len(threeKeys))
    for i := 0; i < len(threeKeys); i++ {
        threeKeys[i], err = testutils.CreateKey(
            serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
        require.NoError(t, err)
        keyIDs[i] = threeKeys[i].ID()
    }
    signedRoot.Signed.Version++
    signedRoot.Signed.Keys[keyIDs[0]] = threeKeys[0]
    signedRoot.Signed.Roles[data.CanonicalRootRole].KeyIDs = []string{keyIDs[0]}
    signedRoot.Signed.Roles[data.CanonicalRootRole].Threshold = 1
    // sign with the first key only
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, []data.PublicKey{threeKeys[0]})

    // Load this root for the first time with 1 key
    require.NoError(t, repo.Update(false))

    // --- First root rotation: replace the first key with a different key
    signedRoot.Signed.Version++
    signedRoot.Signed.Keys[keyIDs[1]] = threeKeys[1]
    signedRoot.Signed.Roles[data.CanonicalRootRole].KeyIDs = []string{keyIDs[1]}

    // --- If the current role is satisfied but the previous one is not, root rotation
    // --- will fail.  Signing with just the second key will not satisfy the first role.
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, []data.PublicKey{threeKeys[1]})
    require.Error(t, repo.Update(false))
    requireRootSignatures(t, serverSwizzler, 1)

    // --- If both the current and previous roles are satisfied, then the root rotation
    // --- will succeed (signing with the first and second keys will satisfy both)
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, threeKeys[:2])
    require.NoError(t, repo.Update(false))
    requireRootSignatures(t, serverSwizzler, 2)

    // --- Second root rotation: replace the second key with a third
    signedRoot.Signed.Version++
    signedRoot.Signed.Keys[keyIDs[2]] = threeKeys[2]
    signedRoot.Signed.Roles[data.CanonicalRootRole].KeyIDs = []string{keyIDs[2]}

    // --- If the current role is satisfied but the previous one is not, root rotation
    // --- will fail.  Signing with just the third key will not satisfy the second role.
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, []data.PublicKey{threeKeys[2]})
    require.Error(t, repo.Update(false))
    requireRootSignatures(t, serverSwizzler, 1)

    // --- If both the current and previous roles are satisfied, then the root rotation
    // --- will succeed (signing with the second and third keys will satisfy both)
    // --- This would fail if the enforced invariant were: all previous roles must sign
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, threeKeys[1:])
    require.NoError(t, repo.Update(false))
    requireRootSignatures(t, serverSwizzler, 2)

    // -- If signed with all previous roles, update will succeed
    signSerializeAndUpdateRoot(t, signedRoot, serverSwizzler, threeKeys)
    require.NoError(t, repo.Update(false))
    requireRootSignatures(t, serverSwizzler, 3)
}

This does the following:

I ran this against master and it passed. If you agree that the test is correct, then old clients are not verifying that they are signed by all previous roots, but rather just that they meet the immediately previous role requirements.

cyli commented 8 years ago

@ecordell The client actually only cares that it matches the previous role that it knows about, not necessarily the immediate previous role. The client doesn't use the previous root roles in the file to verify anything, the previous root roles are only used by a client to know what to sign.

The test above seems very similar to TestValidateRootRotationWithOldRole, in which the client is only ever 1 version behind. I think @endophage may worried about this case instead:

Previously, the writer client would have signed root v3 with A, B, C because it knows about all the old roles. And the reader client that was on root v1 would have successfully been able to download root v3.

However, with this change, the write client would sign root v3 with B, C, and a new reader client would that was on root v1 would just download root v2 and be able to upgrade. An old reader client that was on root v1 would not know to download root v2, though, and hence would reject the root v3 produced by the new client.

I think he is suggesting that the new client sign with A, B, C for a while, to give us time to deprecate older clients.

I understand that the older client can just wipe out their TUF metadata and trust on first use again, but they'd have to do that every time they were > 1 root version behind. It might be a better user experience if we phased out the extra signatures more slowly.

ecordell commented 8 years ago

I think he is suggesting that the new client sign with A, B, C for a while, to give us time to deprecate older clients.

This can still be done with the new client, by specifying all of A, B, C when rotating. If we're talking specifically about the docker integration, the docker client can make the appropriate notary calls behind the scenes.

Is there some reason a phased deployment like that wouldn't work? I prefer this approach because it allows different deployments to choose their own transition timeline.

cyli commented 8 years ago

Do you mean that this can just be solved if the client never remove old keys? Will the new client then sign with all keys that are available in the list, even if it's past the necessary threshold, even if it's not during a root rotation? (e.g. maybe adding a snapshot key).

ecordell commented 8 years ago

Will the new client then sign with all keys that are available in the list, even if it's past the necessary threshold, even if it's not during a root rotation? (e.g. maybe adding a snapshot key).

If you do this with something like notary key rotate test/collection root --key=A --key=B --key=C (where A, B, and C are all previous roots - perhaps A and B and old and C is the newest), then all three are part of the root key list and will be used to sign for any root file change.

Basically to be backwards-compatible you take the previous implicit behavior (sign with all old keys) and make it explicit. You do this for as long as you wish to be backwards-compatible, and then you stop.

Right now I think that this is the most flexible way for clients to handle the transition, but please tell me if I'm missing something important!

cyli commented 8 years ago

Ah ok, that makes sense. I do like that you have to be explicit about things. It might be awkward that you have to keep the public certs around so that you can keep the keys in place for rotation, but maybe we can write a nice export function or tool. Cool, thanks!

endophage commented 8 years ago

I'm wondering if it would make more sense to invert the concept of which keys to sign with and instead users specify which ones to drop. I'm thinking that may allow for more intelligent default behaviour, i.e. you always want to sign with the immediately previous keys, so you should never have to specify those. By specifying keys to drop, the rotation becomes the point at which you specify the keys you want to phase out.

Thoughts?

riyazdf commented 8 years ago

@ecordell I got the chance to take a second look at #942, thank you for all of your work on that PR and addressing our feedback!

We're still concerned about backwards compatibility and curious on your thoughts about @endophage's suggestion in the previous comment?

ecordell commented 8 years ago

Sorry for the delay getting back here!

I like @endophage's suggestion as an additional interface but not as the only one, mainly because I'm of the opinion that the best UX is to remove old keys entirely when possible. I think supporting both makes sense so that the transition is mostly a documentation issue:

For some defined period, docs say:

to rotate keys, run notary key rotate gun root --drop=old --key=new

Eventually change to:

to rotate keys, run notary key rotate gun root --key=new

i.e. the behavior of not specifying --drop is to drop all keys. Perhaps it would be clearer to have the option be --only-drop?

Does that sound good?

endophage commented 8 years ago

If the expectation is that notary key rotate <gun> root --key=new will always sign with the immediately previous keys, while replacing them and signing with the key specified as new, then yes, I think this works.

I don't think it was addressed on the WIP PR but we're also very opposed to maintaining the old keys in the existing root role for legacy support. If your key has been compromised, this prevents an effective root rotation that endangers all clients, old and new. I would be fine with only supporting root key rotation publishing with new clients (so we can remove the root.1, etc... from the root.json), and additionally working out something that allows older client to continue understanding root key rotations, bearing in mind multiple rotations may happen between client updates.

Possibly it would be a good first step to implement the version fetching for publishing which would let us deploy and test the server functionality of that feature. Having a more interactive root key rotation UX would be acceptable if not desirable, so we could have some logic to go fetch paginated versions and present the user with an interactive UX that asks them how far back they want to go in history, fetching more "pages" if necessary, until we aggregate some collection of root keys they want to continue signing with for this rotation. If we can cache the old root versions, subsequent signing for non-root rotation reasons (i.e. new targets key), can iterate the old roots efficiently to translate from TUF key IDs to canonical IDs.

Open to discussion on all of this. The only hard requirement is existing clients can't break on read.

ecordell commented 8 years ago

If the expectation is that notary key rotate root --key=new will always sign with the immediately previous keys, while replacing them and signing with the key specified as new, then yes, I think this works.

Yep! That's the plan. As for your other concerns I believe they are addressed in the current PR. All that is lacking is the drop flag.

HuKeping commented 7 years ago

how do we prevent things from being deleted by accident: i.e. assuming threshold > 1, targets v1 is empty and signed by necessary threshold. User A generates v2, adds target "a", and pushes that partially signed targets file to server. User B wants to add target "b". Does he pull the untrusted (lacking threshold signatures) v2, does he pull v1 and create a conflicting v2, or does he pull v1 in the knowledge there is a v2 and he should create v3 but without the as yet untrusted "a" target? Alternatively do we block User B from pushing until v2 is finalized?

I think Alternatively do we block User B from pushing until v2 is finalized may not be a proper way, that when one of the key compromised, people can use it to stop all the update for the target(by flooding server with malicious v2 ).

HuKeping commented 7 years ago

If you request the metadata, it's either valid (meets threshold/keys) or invalid (doesn't meet threshold). If it's not valid and you're a "normal" user or a deployment system, you stop - the TUF repo is currently in an update process. There could be a mechanism to pull the latest valid metadata so that users don't have to be blocked (though some may wish to be in this case) or we could have explicitly staged metadata.

Image you have a TUF repo, let's take it simple, you have a docker image repo, named "docker.io/official/ubuntu:14.04". One day you find that there is a CVE about the current image ubuntu14.04, so you push a new ubuntu:14.04 to the server, but as the threshold is 2, your publish is invalid for others at present.

If we have the mechanism that allowed to pull the latest valid metadata, yes it would not block the process but people will get the CVE version of ubuntu:14.04 which you'd like to deprecated with .

ecordell commented 7 years ago

@HuKeping I think whatever mechanism we use for allowing people to pull down partially signed metadata will cover the case you're concerned about. If you know there's a new ubuntu that hasn't been fully signed and want to go ahead and pull, you can do that.

That said, it won't be considered the "latest valid metadata". By definition metadata with an unmet threshold is invalid. If the vetting process for a package only requires one signature, it shouldn't use a threshold of 2 for that role in the first place, IMO.