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

Fix auth login error messages and messaging when witnessing a delegation with 0 valid keys #972

Merged
merged 5 commits into from
Sep 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## [v0.4.0](https://github.com/docker/notary/releases/tag/v0.4.0) 8/11/2016
## [v0.4.1](https://github.com/docker/notary/releases/tag/v0.4.0) 9/27/2016
+ Preliminary Windows support for notary client [#970](https://github.com/docker/notary/pull/970)
+ Output message to CLI when repo changes have been successfully published [#974](https://github.com/docker/notary/pull/974)
+ Improved error messages for client authentication errors and for the witness command [#972](https://github.com/docker/notary/pull/972)

## [v0.4.0](https://github.com/docker/notary/releases/tag/v0.4.0) 9/21/2016
+ Server-managed key rotations [#889](https://github.com/docker/notary/pull/889)
+ Remove `timestamp_keys` table, which stored redundant information [#889](https://github.com/docker/notary/pull/889)
+ Introduce `notary delete` command to delete local and/or remote repo data [#895](https://github.com/docker/notary/pull/895)
Expand Down
103 changes: 69 additions & 34 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion NOTARY_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4
0.4.1
16 changes: 14 additions & 2 deletions client/witness.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package client

import (
"path/filepath"

"github.com/docker/notary/client/changelist"
"github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data"
"path/filepath"
)

// Witness creates change objects to witness (i.e. re-sign) the given
Expand Down Expand Up @@ -41,7 +42,18 @@ func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error {
r.Dirty = true
return nil
}
if invalid != nil {

if roleObj, err := repo.GetDelegationRole(role); err == nil && invalid != nil {
// A role with a threshold > len(keys) is technically invalid, but we let it build in the builder because
// we want to be able to download the role (which may still have targets on it), add more keys, and then
// witness the role, thus bringing it back to valid. However, if no keys have been added before witnessing,
// then it is still an invalid role, and can't be witnessed because nothing can bring it back to valid.
if roleObj.Threshold > len(roleObj.Keys) {
return data.ErrInvalidRole{
Role: role,
Reason: "role does not specify enough valid signing keys to meet its required threshold",
}
}
if r, ok := invalid.Targets[role]; ok {
// role is recognized but invalid, move to valid data and mark for re-signing
repo.Targets[role] = r
Expand Down
8 changes: 8 additions & 0 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,8 @@ func TestPurgeSingleKey(t *testing.T) {
// 11. witness an invalid role and check for error on publish
// 12. check non-targets base roles all fail
// 13. test auto-publish functionality
// 14. remove all keys from the delegation and publish
// 15. witnessing the delegation should now fail
func TestWitness(t *testing.T) {
setUp(t)

Expand Down Expand Up @@ -1637,6 +1639,12 @@ func TestWitness(t *testing.T) {
require.NoError(t, err)
require.Contains(t, output, targetName)
require.Contains(t, output, targetHash)

_, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "remove", "-p", "gun", delgName, keyID, keyID2)
require.NoError(t, err)
_, err = runCommand(t, tempDir, "-s", server.URL, "witness", "-p", "gun", delgName)
require.Error(t, err)
require.Contains(t, err.Error(), "role does not specify enough valid signing keys to meet its required threshold")
Copy link
Contributor

Choose a reason for hiding this comment

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

awesome, thanks for adding this test 👍

}

func generateCertPrivKeyPair(t *testing.T, gun, keyAlgorithm string) (*x509.Certificate, data.PrivateKey, string) {
Expand Down
14 changes: 13 additions & 1 deletion cmd/notary/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"strings"
"time"

"golang.org/x/crypto/ssh/terminal"

"github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
Expand Down Expand Up @@ -755,7 +757,8 @@ type passwordStore struct {
}

func (ps passwordStore) Basic(u *url.URL) (string, string) {
if ps.anonymous {
// if it's not a terminal, don't wait on input
if ps.anonymous || !terminal.IsTerminal(int(os.Stdin.Fd())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we be checking the input that was set on the command?

Copy link
Contributor

Choose a reason for hiding this comment

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

IRL: leave as is for now as we use os.Stdin elsewhere too. Issue open already for looking into this.

return "", ""
}

Expand Down Expand Up @@ -795,6 +798,15 @@ func (ps passwordStore) Basic(u *url.URL) (string, string) {
return username, password
}

// to comply with the CredentialStore interface
func (ps passwordStore) RefreshToken(u *url.URL, service string) string {
return ""
}

// to comply with the CredentialStore interface
func (ps passwordStore) SetRefreshToken(u *url.URL, service string, token string) {
}

type httpAccess int

const (
Expand Down
17 changes: 17 additions & 0 deletions cmd/notary/tuf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package main
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"

"github.com/docker/distribution/registry/client/auth"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -219,3 +221,18 @@ func TestGetTrustPinningErrors(t *testing.T) {
tc.sha256 = "88b76b34ab83a9e4d5abe3697950fb73f940aab1aa5b534f80cf9de9708942be"
require.Error(t, tc.tufAddByHash(&cobra.Command{}, []string{"gun", "test1", "100"}))
}

func TestPasswordStore(t *testing.T) {
myurl, err := url.Parse("https://docker.io")
require.NoError(t, err)

// whether or not we're anonymous, because this isn't a terminal,
for _, ps := range []auth.CredentialStore{passwordStore{}, passwordStore{anonymous: true}} {
username, passwd := ps.Basic(myurl)
require.Equal(t, "", username)
require.Equal(t, "", passwd)

ps.SetRefreshToken(myurl, "someService", "token") // doesn't return an error, just want to make sure no state changes
require.Equal(t, "", ps.RefreshToken(myurl, "someService"))
}
}
13 changes: 9 additions & 4 deletions tuf/signed/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ type ErrInsufficientSignatures struct {
}

func (e ErrInsufficientSignatures) Error() string {
candidates := strings.Join(e.MissingKeyIDs, ", ")
candidates := ""
if len(e.MissingKeyIDs) > 0 {
candidates = fmt.Sprintf(" (%s)", strings.Join(e.MissingKeyIDs, ", "))
}

if e.FoundKeys == 0 {
return fmt.Sprintf("signing keys not available, need %d keys out of: %s", e.NeededKeys, candidates)
return fmt.Sprintf("signing keys not available: need %d keys from %d possible keys%s",
e.NeededKeys, len(e.MissingKeyIDs), candidates)
}
return fmt.Sprintf("not enough signing keys: got %d of %d needed keys, other candidates: %s",
e.FoundKeys, e.NeededKeys, candidates)
return fmt.Sprintf("not enough signing keys: found %d of %d needed keys - %d other possible keys%s",
e.FoundKeys, e.NeededKeys, len(e.MissingKeyIDs), candidates)
}

// ErrExpired indicates a piece of metadata has expired
Expand Down
13 changes: 13 additions & 0 deletions tuf/signed/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,16 @@ func TestSignFailingKeys(t *testing.T) {
require.Error(t, err)
require.IsType(t, FailingPrivateKeyErr{}, err)
}

// make sure we produce readable error messages
func TestErrInsufficientSignaturesMessaging(t *testing.T) {
require.Contains(t,
ErrInsufficientSignatures{NeededKeys: 2, MissingKeyIDs: []string{"ID1", "ID2"}}.Error(),
"need 2 keys from 2 possible keys (ID1, ID2)")
require.Contains(t,
ErrInsufficientSignatures{FoundKeys: 1, NeededKeys: 2, MissingKeyIDs: []string{"ID1", "ID2"}}.Error(),
"found 1 of 2 needed keys - 2 other possible keys (ID1, ID2)")
require.Contains(t,
ErrInsufficientSignatures{FoundKeys: 1, NeededKeys: 2, MissingKeyIDs: []string{}}.Error(),
"found 1 of 2 needed keys - 0 other possible keys")
}
38 changes: 0 additions & 38 deletions vendor/github.com/docker/distribution/.drone.yml

This file was deleted.

6 changes: 5 additions & 1 deletion vendor/github.com/docker/distribution/.mailmap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading