1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #19760 from cyli/re-vendor-notary

Re-vendor Notary and docker/go
This commit is contained in:
Jess Frazelle 2016-01-27 09:36:26 -08:00
commit c39c7e6edf
43 changed files with 368 additions and 172 deletions

View file

@ -167,7 +167,7 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-3
ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -110,11 +110,11 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_COMMIT 0c11a970826e62479379ccc75a45184460b9200f
ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH"

View file

@ -144,7 +144,7 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_VERSION docker-v1.10-2
ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -123,11 +123,11 @@ RUN set -x \
# TODO update this when we upgrade to Go 1.5.1+
# Install notary server
#ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
#ENV NOTARY_VERSION docker-v1.10-4
#RUN set -x \
# && export GOPATH="$(mktemp -d)" \
# && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
# && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
# && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
# && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
# go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
# && rm -rf "$GOPATH"

View file

@ -116,11 +116,11 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install notary server
ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH"

View file

@ -50,11 +50,11 @@ clone git github.com/docker/distribution c301f8ab27f4913c968b8d73a38e5dda79b9d3d
clone git github.com/vbatts/tar-split v0.9.11
# get desired notary commit, might also need to be updated in Dockerfile
clone git github.com/docker/notary docker-v1.10-3
clone git github.com/docker/notary docker-v1.10-4
clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
clone git github.com/jfrazelle/go v1.5.1-1
clone git github.com/docker/go v1.5.1-1-1-gbaf439e
clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
clone git github.com/opencontainers/runc 3d8a20bb772defc28c355534d83486416d1719b4 # libcontainer

View file

@ -301,18 +301,19 @@ func (s *DockerTrustSuite) TestTrustedCreate(c *check.C) {
}
func (s *DockerTrustSuite) TestUntrustedCreate(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockercliuntrusted/createtest", privateRegistryURL)
withTagName := fmt.Sprintf("%s:latest", repoName)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
dockerCmd(c, "push", repoName)
dockerCmd(c, "rmi", repoName)
dockerCmd(c, "tag", "busybox", withTagName)
dockerCmd(c, "push", withTagName)
dockerCmd(c, "rmi", withTagName)
// Try trusted create on untrusted tag
createCmd := exec.Command(dockerBinary, "create", repoName)
createCmd := exec.Command(dockerBinary, "create", withTagName)
s.trustedCmd(createCmd)
out, _, err := runCommandWithOutput(createCmd)
c.Assert(err, check.Not(check.IsNil))
c.Assert(string(out), checker.Contains, "trust data unavailable. Has a notary repository been initialized?", check.Commentf("Missing expected output on trusted create:\n%s", out))
c.Assert(string(out), checker.Contains, fmt.Sprintf("does not have trust data for %s", repoName), check.Commentf("Missing expected output on trusted create:\n%s", out))
}

View file

@ -47,7 +47,7 @@ func (s *DockerTrustSuite) TestTrustedIsolatedPull(c *check.C) {
}
func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockercliuntrusted/pulltest:latest", privateRegistryURL)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
dockerCmd(c, "push", repoName)

View file

@ -215,7 +215,7 @@ func (s *DockerSchema1RegistrySuite) TestCrossRepositoryLayerPushNotSupported(c
}
func (s *DockerTrustSuite) TestTrustedPush(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockerclitrusted/pushtest:latest", privateRegistryURL)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
@ -267,7 +267,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithDeprecatedEnvPasswords(c *check.C)
}
func (s *DockerTrustSuite) TestTrustedPushWithFailingServer(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockerclitrusted/failingserver:latest", privateRegistryURL)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
@ -279,7 +279,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithFailingServer(c *check.C) {
}
func (s *DockerTrustSuite) TestTrustedPushWithoutServerAndUntrusted(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockerclitrusted/trustedandnot:latest", privateRegistryURL)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)

View file

@ -3179,7 +3179,7 @@ func (s *DockerTrustSuite) TestTrustedRun(c *check.C) {
func (s *DockerTrustSuite) TestUntrustedRun(c *check.C) {
// Windows does not support this functionality
testRequires(c, DaemonIsLinux)
repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
repoName := fmt.Sprintf("%v/dockercliuntrusted/runtest:latest", privateRegistryURL)
// tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName)
dockerCmd(c, "push", repoName)

View file

@ -118,12 +118,13 @@ protos:
# be run first
define gocover
$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).cover" "$(1)" || exit 1;
$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
endef
gen-cover: go_version
@mkdir -p "$(COVERDIR)"
$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
rm "$(COVERDIR)"/*testutils*.coverage.txt
# Generates the cover binaries and runs them all in serial, so this can be used
# run all tests with a yubikey without any problems

View file

@ -4,7 +4,6 @@ import (
"crypto/x509"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/Sirupsen/logrus"
@ -13,14 +12,6 @@ import (
"github.com/docker/notary/tuf/signed"
)
// Manager is an abstraction around trusted root CA stores
type Manager struct {
trustedCAStore trustmanager.X509Store
trustedCertificateStore trustmanager.X509Store
}
const trustDir = "trusted_certificates"
// ErrValidationFail is returned when there is no valid trusted certificates
// being served inside of the roots.json
type ErrValidationFail struct {
@ -45,63 +36,6 @@ func (err ErrRootRotationFail) Error() string {
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
}
// NewManager returns an initialized Manager, or an error
// if it fails to load certificates
func NewManager(baseDir string) (*Manager, error) {
trustPath := filepath.Join(baseDir, trustDir)
// Load all CAs that aren't expired and don't use SHA1
trustedCAStore, err := trustmanager.NewX509FilteredFileStore(trustPath, func(cert *x509.Certificate) bool {
return cert.IsCA && cert.BasicConstraintsValid && cert.SubjectKeyId != nil &&
time.Now().Before(cert.NotAfter) &&
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
})
if err != nil {
return nil, err
}
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
trustedCertificateStore, err := trustmanager.NewX509FilteredFileStore(trustPath, func(cert *x509.Certificate) bool {
return !cert.IsCA &&
time.Now().Before(cert.NotAfter) &&
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
})
if err != nil {
return nil, err
}
return &Manager{
trustedCAStore: trustedCAStore,
trustedCertificateStore: trustedCertificateStore,
}, nil
}
// TrustedCertificateStore returns the trusted certificate store being managed
// by this Manager
func (m *Manager) TrustedCertificateStore() trustmanager.X509Store {
return m.trustedCertificateStore
}
// TrustedCAStore returns the CA store being managed by this Manager
func (m *Manager) TrustedCAStore() trustmanager.X509Store {
return m.trustedCAStore
}
// AddTrustedCert adds a cert to the trusted certificate store (not the CA
// store)
func (m *Manager) AddTrustedCert(cert *x509.Certificate) {
m.trustedCertificateStore.AddCert(cert)
}
// AddTrustedCACert adds a cert to the trusted CA certificate store
func (m *Manager) AddTrustedCACert(cert *x509.Certificate) {
m.trustedCAStore.AddCert(cert)
}
/*
ValidateRoot receives a new root, validates its correctness and attempts to
do root key rotation if needed.
@ -111,7 +45,7 @@ that list is non-empty means that we've already seen this repository before, and
have a list of trusted certificates for it. In this case, we use this list of
certificates to attempt to validate this root file.
If the previous validation suceeds, or in the case where we found no trusted
If the previous validation succeeds, or in the case where we found no trusted
certificates for this particular GUN, we check the integrity of the root by
making sure that it is validated by itself. This means that we will attempt to
validate the root data with the certificates that are included in the root keys
@ -129,7 +63,7 @@ we are using the current public PKI to validate the first download of the certif
adding an extra layer of security over the normal (SSH style) trust model.
We shall call this: TOFUS.
*/
func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string) error {
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
signedRoot, err := data.RootFromSigned(root)
if err != nil {
@ -144,7 +78,7 @@ func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
}
// Retrieve all the trusted certificates that match this gun
certsForCN, err := m.trustedCertificateStore.GetCertificatesByCN(gun)
certsForCN, err := certStore.GetCertificatesByCN(gun)
if err != nil {
// If the error that we get back is different than ErrNoCertificatesFound
// we couldn't check if there are any certificates with this CN already
@ -183,7 +117,7 @@ func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
// Do root certificate rotation: we trust only the certs present in the new root
// First we add all the new certificates (even if they already exist)
for _, cert := range allValidCerts {
err := m.trustedCertificateStore.AddCert(cert)
err := certStore.AddCert(cert)
if err != nil {
// If the error is already exists we don't fail the rotation
if _, ok := err.(*trustmanager.ErrCertExists); ok {
@ -197,7 +131,7 @@ func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
// Now we delete old certificates that aren't present in the new root
for certID, cert := range certsToRemove(certsForCN, allValidCerts) {
logrus.Debugf("removing certificate with certID: %s", certID)
err = m.trustedCertificateStore.RemoveCert(cert)
err = certStore.RemoveCert(cert)
if err != nil {
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
@ -208,7 +142,7 @@ func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
return nil
}
// validRootLeafCerts returns a list of non-exipired, non-sha1 certificates whoose
// validRootLeafCerts returns a list of non-exipired, non-sha1 certificates whose
// Common-Names match the provided GUN
func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) {
// Get a list of all of the leaf certificates present in root
@ -219,7 +153,8 @@ func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate,
for _, cert := range allLeafCerts {
// Validate that this leaf certificate has a CN that matches the exact gun
if cert.Subject.CommonName != gun {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s", cert.Subject.CommonName)
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
cert.Subject.CommonName, gun)
continue
}
// Make sure the certificate is not expired

View file

@ -18,6 +18,8 @@ machine:
CIRCLE_PAIN: "mode: set"
# Put the coverage profile somewhere codecov's script can find it
COVERPROFILE: coverage.out
# Set the pull request number so codecov can figure it out
PULL_REQUEST: ${CI_PULL_REQUEST##*/}
hosts:
# Not used yet
@ -40,8 +42,7 @@ dependencies:
# For the stable go version, additionally install linting tools
- >
gvm use stable &&
go get github.com/golang/lint/golint github.com/wadey/gocovmerge &&
go install github.com/wadey/gocovmerge
go get github.com/golang/lint/golint
test:
pre:
# Output the go versions we are going to test
@ -72,11 +73,6 @@ test:
pwd: $BASE_STABLE
post:
- gvm use stable && make covmerge:
timeout: 600
parallel: true
pwd: $BASE_STABLE
# Report to codecov.io
- bash <(curl -s https://codecov.io/bash):
parallel: true

View file

@ -9,10 +9,10 @@ import (
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/notary"
"github.com/docker/notary/certs"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
@ -53,9 +53,9 @@ type ErrInvalidRemoteRole struct {
Role string
}
func (e ErrInvalidRemoteRole) Error() string {
func (err ErrInvalidRemoteRole) Error() string {
return fmt.Sprintf(
"notary does not support the server managing the %s key", e.Role)
"notary does not support the server managing the %s key", err.Role)
}
// ErrRepositoryNotExist is returned when an action is taken on a remote
@ -84,7 +84,7 @@ type NotaryRepository struct {
CryptoService signed.CryptoService
tufRepo *tuf.Repo
roundTrip http.RoundTripper
CertManager *certs.Manager
CertStore trustmanager.X509Store
}
// repositoryFromKeystores is a helper function for NewNotaryRepository that
@ -93,7 +93,11 @@ type NotaryRepository struct {
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
keyStores []trustmanager.KeyStore) (*NotaryRepository, error) {
certManager, err := certs.NewManager(baseDir)
certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return nil, err
}
@ -107,7 +111,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
CryptoService: cryptoService,
roundTrip: rt,
CertManager: certManager,
CertStore: certStore,
}
fileStore, err := store.NewFilesystemStore(
@ -165,7 +169,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
// currently we only support server managing timestamps and snapshots, and
// nothing else - timestamps are always managed by the server, and implicit
// (do not have to be passed in as part of `serverManagedRoles`, so that
// the API of Initialize doens't change).
// the API of Initialize doesn't change).
var serverManagesSnapshot bool
locallyManagedKeys := []string{
data.CanonicalTargetsRole,
@ -197,7 +201,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
if err != nil {
return err
}
r.CertManager.AddTrustedCert(rootCert)
r.CertStore.AddCert(rootCert)
// The root key gets stored in the TUF metadata X509 encoded, linking
// the tuf root.json to our X509 PKI.
@ -275,8 +279,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
var changes []changelist.Change
for _, role := range roles {
role = strings.ToLower(role)
// Ensure we can only add targets to the CanonicalTargetsRole,
// or a Delegation role (which is <CanonicalTargetsRole>/something else)
if role != data.CanonicalTargetsRole && !data.IsDelegation(role) {
@ -347,7 +349,7 @@ func (r *NotaryRepository) AddDelegation(name string, threshold int,
// the repository when the changelist gets applied at publish time.
// This does not validate that the delegation exists, since one might exist
// after applying all changes.
func (r *NotaryRepository) RemoveDelegation(name string) error {
func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error {
if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
@ -360,20 +362,41 @@ func (r *NotaryRepository) RemoveDelegation(name string) error {
defer cl.Close()
logrus.Debugf(`Removing delegation "%s"\n`, name)
var template *changelist.TufChange
template := changelist.NewTufChange(
changelist.ActionDelete,
name,
changelist.TypeTargetsDelegation,
"", // no path
nil,
)
// We use the Delete action only for force removal, Update is used for removing individual keys and paths
if removeAll {
template = changelist.NewTufChange(
changelist.ActionDelete,
name,
changelist.TypeTargetsDelegation,
"", // no path
nil, // deleting role, no data needed
)
} else {
tdJSON, err := json.Marshal(&changelist.TufDelegation{
RemoveKeys: keyIDs,
RemovePaths: paths,
})
if err != nil {
return err
}
template = changelist.NewTufChange(
changelist.ActionUpdate,
name,
changelist.TypeTargetsDelegation,
"", // no path
tdJSON,
)
}
return addChange(cl, template, name)
}
// AddTarget creates new changelist entries to add a target to the given roles
// in the repository when the changelist gets appied at publish time.
// in the repository when the changelist gets applied at publish time.
// If roles are unspecified, the default role is "targets".
func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {
@ -431,7 +454,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
for _, role := range roles {
// we don't need to do anything special with removing role from
// roles because listSubtree always processes role and only excludes
// descendent delegations that appear in roles.
// descendant delegations that appear in roles.
r.listSubtree(targets, role, roles...)
}
@ -509,6 +532,92 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
return cl, nil
}
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
// Update state of the repo to latest
if _, err := r.Update(false); err != nil {
return nil, err
}
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
if !ok {
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
}
allDelegations := targets.Signed.Delegations.Roles
// make a copy for traversing nested delegations
delegationsList := make([]*data.Role, len(allDelegations))
copy(delegationsList, allDelegations)
// Now traverse to lower level delegations (ex: targets/level1/level2)
for len(delegationsList) > 0 {
// Pop off first delegation to traverse
delegation := delegationsList[0]
delegationsList = delegationsList[1:]
// Get metadata
delegationMeta, ok := r.tufRepo.Targets[delegation.Name]
// If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed
if !ok {
continue
}
// Add nested delegations to return list and exploration list
allDelegations = append(allDelegations, delegationMeta.Signed.Delegations.Roles...)
delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...)
}
return allDelegations, nil
}
// RoleWithSignatures is a Role with its associated signatures
type RoleWithSignatures struct {
Signatures []data.Signature
data.Role
}
// ListRoles returns a list of RoleWithSignatures objects for this repo
// This represents the latest metadata for each role in this repo
func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
// Update to latest repo state
_, err := r.Update(false)
if err != nil {
return nil, err
}
// Get all role info from our updated keysDB, can be empty
roles := r.tufRepo.GetAllLoadedRoles()
var roleWithSigs []RoleWithSignatures
// Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata
for _, role := range roles {
roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil}
switch role.Name {
case data.CanonicalRootRole:
roleWithSig.Signatures = r.tufRepo.Root.Signatures
case data.CanonicalTargetsRole:
roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures
case data.CanonicalSnapshotRole:
roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures
case data.CanonicalTimestampRole:
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
default:
// If the role isn't a delegation, we should error -- this is only possible if we have invalid keyDB state
if !data.IsDelegation(role.Name) {
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
}
if _, ok := r.tufRepo.Targets[role.Name]; ok {
// We'll only find a signature if we've published any targets with this delegation
roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures
}
}
roleWithSigs = append(roleWithSigs, roleWithSig)
}
return roleWithSigs, nil
}
// Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) Publish() error {
@ -837,7 +946,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
return nil, err
}
err = r.CertManager.ValidateRoot(root, r.gun)
err = certs.ValidateRoot(r.CertStore, root, r.gun)
if err != nil {
return nil, err
}
@ -904,3 +1013,27 @@ func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.Publi
}
return nil
}
// DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side
func (r *NotaryRepository) DeleteTrustData() error {
// Clear TUF files and cache
if err := r.fileStore.RemoveAll(); err != nil {
return fmt.Errorf("error clearing TUF repo data: %v", err)
}
r.tufRepo = tuf.NewRepo(nil, nil)
// Clear certificates
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
if err != nil {
// If there were no certificates to delete, we're done
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); ok {
return nil
}
return fmt.Errorf("error retrieving certificates for %s: %v", r.gun, err)
}
for _, cert := range certificates {
if err := r.CertStore.RemoveCert(cert); err != nil {
return fmt.Errorf("error removing certificate: %v: %v", cert, err)
}
}
return nil
}

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"path"
"strings"
"time"
"github.com/Sirupsen/logrus"
@ -85,13 +86,13 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
return err
}
if err == nil {
// role existed
return data.ErrInvalidRole{
Role: c.Scope(),
Reason: "cannot create a role that already exists",
// role existed, attempt to merge paths and keys
if err := r.AddPaths(td.AddPaths); err != nil {
return err
}
return repo.UpdateDelegations(r, td.AddKeys)
}
// role doesn't exist, create brand new
// create brand new role
r, err = td.ToNewRole(c.Scope())
if err != nil {
return err
@ -107,7 +108,12 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
if err != nil {
return err
}
// role exists, merge
// If we specify the only keys left delete the role, else just delete specified keys
if strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && len(td.AddKeys) == 0 {
r := data.Role{Name: c.Scope()}
return repo.DeleteDelegation(r)
}
// if we aren't deleting and the role exists, merge
if err := r.AddPaths(td.AddPaths); err != nil {
return err
}

View file

@ -2,6 +2,14 @@ package notary
// application wide constants
const (
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
MinThreshold = 1
// PrivKeyPerms are the file permissions to use when writing private keys to disk
PrivKeyPerms = 0700
// PubCertPerms are the file permissions to use when writing public certificates to disk
PubCertPerms = 0755
// Sha256HexSize is how big a Sha256 hex is in number of characters
Sha256HexSize = 64
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
TrustedCertsDir = "trusted_certificates"
)

View file

@ -69,8 +69,8 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err)
}
return nil, fmt.Errorf("keystores would not accept new private keys for unknown reasons")
return nil, fmt.Errorf("keystores would not accept new private keys for unknown reasons")
}
// GetPrivateKey returns a private key and role if present by ID.

View file

@ -205,7 +205,8 @@ func GetLeafCerts(certs []*x509.Certificate) []*x509.Certificate {
// GetIntermediateCerts parses a list of x509 Certificates and returns all of the
// ones marked as a CA, to be used as intermediates
func GetIntermediateCerts(certs []*x509.Certificate) (intCerts []*x509.Certificate) {
func GetIntermediateCerts(certs []*x509.Certificate) []*x509.Certificate {
var intCerts []*x509.Certificate
for _, cert := range certs {
if cert.IsCA {
intCerts = append(intCerts, cert)
@ -299,6 +300,44 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
}
}
// ParsePEMPublicKey returns a data.PublicKey from a PEM encoded public key or certificate.
func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
pemBlock, _ := pem.Decode(pubKeyBytes)
if pemBlock == nil {
return nil, errors.New("no valid public key found")
}
switch pemBlock.Type {
case "CERTIFICATE":
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("could not parse provided certificate: %v", err)
}
err = ValidateCertificate(cert)
if err != nil {
return nil, fmt.Errorf("invalid certificate: %v", err)
}
return CertToKey(cert), nil
default:
return nil, fmt.Errorf("unsupported PEM block type %q, expected certificate", pemBlock.Type)
}
}
// ValidateCertificate returns an error if the certificate is not valid for notary
// Currently, this is only a time expiry check
func ValidateCertificate(c *x509.Certificate) error {
if (c.NotBefore).After(c.NotAfter) {
return fmt.Errorf("certificate validity window is invalid")
}
now := time.Now()
tomorrow := now.AddDate(0, 0, 1)
// Give one day leeway on creation "before" time, check "after" against today
if (tomorrow).Before(c.NotBefore) || now.After(c.NotAfter) {
return fmt.Errorf("certificate is expired")
}
return nil
}
// GenerateRSAKey generates an RSA private key and returns a TUF PrivateKey
func GenerateRSAKey(random io.Reader, bits int) (data.PrivateKey, error) {
rsaPrivKey, err := rsa.GenerateKey(random, bits)
@ -532,3 +571,14 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
return key.ID(), nil
}
// FilterCertsExpiredSha1 can be used as the filter function to cert store
// initializers to filter out all expired or SHA-1 certificate that we
// shouldn't load.
func FilterCertsExpiredSha1(cert *x509.Certificate) bool {
return !cert.IsCA &&
time.Now().Before(cert.NotAfter) &&
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
}

View file

@ -54,7 +54,7 @@ func (c *Client) Update() error {
if err != nil {
logrus.Debug("Error occurred. Root will be downloaded and another update attempted")
if err := c.downloadRoot(); err != nil {
logrus.Error("client Update (Root):", err)
logrus.Error("Client Update (Root):", err)
return err
}
// If we error again, we now have the latest root and just want to fail
@ -247,28 +247,27 @@ func (c *Client) downloadTimestamp() error {
// We may not have a cached timestamp if this is the first time
// we're interacting with the repo. This will result in the
// version being 0
var download bool
old := &data.Signed{}
version := 0
var (
saveToCache bool
old *data.Signed
version = 0
)
cachedTS, err := c.cache.GetMeta(role, maxSize)
if err == nil {
err := json.Unmarshal(cachedTS, old)
cached := &data.Signed{}
err := json.Unmarshal(cachedTS, cached)
if err == nil {
ts, err := data.TimestampFromSigned(old)
ts, err := data.TimestampFromSigned(cached)
if err == nil {
version = ts.Signed.Version
}
} else {
old = nil
old = cached
}
}
// unlike root, targets and snapshot, always try and download timestamps
// from remote, only using the cache one if we couldn't reach remote.
raw, s, err := c.downloadSigned(role, maxSize, nil)
if err != nil || len(raw) == 0 {
if err, ok := err.(store.ErrMetaNotFound); ok {
return err
}
if old == nil {
if err == nil {
// couldn't retrieve data from server and don't have valid
@ -277,17 +276,18 @@ func (c *Client) downloadTimestamp() error {
}
return err
}
logrus.Debug("using cached timestamp")
logrus.Debug(err.Error())
logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
s = old
} else {
download = true
saveToCache = true
}
err = signed.Verify(s, role, version, c.keysDB)
if err != nil {
return err
}
logrus.Debug("successfully verified timestamp")
if download {
if saveToCache {
c.cache.SetMeta(role, raw)
}
ts, err := data.TimestampFromSigned(s)
@ -327,7 +327,7 @@ func (c *Client) downloadSnapshot() error {
}
err := json.Unmarshal(raw, old)
if err == nil {
snap, err := data.TimestampFromSigned(old)
snap, err := data.SnapshotFromSigned(old)
if err == nil {
version = snap.Signed.Version
} else {

View file

@ -14,7 +14,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/agl/ed25519"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// PublicKey is the necessary interface for public keys

View file

@ -2,6 +2,7 @@ package data
import (
"fmt"
"github.com/Sirupsen/logrus"
"path"
"regexp"
"strings"
@ -109,10 +110,7 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin
}
if IsDelegation(name) {
if len(paths) == 0 && len(pathHashPrefixes) == 0 {
return nil, ErrInvalidRole{
Role: name,
Reason: "roles with no Paths and no PathHashPrefixes will never be able to publish content",
}
logrus.Debugf("role %s with no Paths and no PathHashPrefixes will never be able to publish content until one or more are added", name)
}
}
if threshold < 1 {

View file

@ -3,7 +3,7 @@ package data
import (
"time"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// SignedRoot is a fully unpacked root.json

View file

@ -1,6 +1,6 @@
package data
import "github.com/jfrazelle/go/canonical/json"
import "github.com/docker/go/canonical/json"
// Serializer is an interface that can marshal and unmarshal TUF data. This
// is expected to be a canonical JSON marshaller

View file

@ -5,7 +5,7 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// SignedSnapshot is a fully unpacked snapshot.json

View file

@ -5,7 +5,7 @@ import (
"encoding/hex"
"errors"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// SignedTargets is a fully unpacked targets.json, or target delegation

View file

@ -4,7 +4,7 @@ import (
"bytes"
"time"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// SignedTimestamp is a fully unpacked timestamp.json

View file

@ -11,7 +11,7 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/jfrazelle/go/canonical/json"
"github.com/docker/go/canonical/json"
)
// SigAlgorithm for types of signatures

View file

@ -58,6 +58,15 @@ func (db *KeyDB) AddRole(r *data.Role) error {
return nil
}
// GetAllRoles gets all roles from the database
func (db *KeyDB) GetAllRoles() []*data.Role {
roles := []*data.Role{}
for _, role := range db.roles {
roles = append(roles, role)
}
return roles
}
// GetKey pulls a key out of the database by its ID
func (db *KeyDB) GetKey(id string) data.PublicKey {
return db.keys[id]

View file

@ -6,9 +6,9 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/go/canonical/json"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/keys"
"github.com/jfrazelle/go/canonical/json"
)
// Various basic signing errors

View file

@ -39,11 +39,14 @@ type FilesystemStore struct {
targetsDir string
}
func (f *FilesystemStore) getPath(name string) string {
fileName := fmt.Sprintf("%s.%s", name, f.metaExtension)
return filepath.Join(f.metaDir, fileName)
}
// GetMeta returns the meta for the given name (a role)
func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
fileName := fmt.Sprintf("%s.%s", name, f.metaExtension)
path := filepath.Join(f.metaDir, fileName)
meta, err := ioutil.ReadFile(path)
meta, err := ioutil.ReadFile(f.getPath(name))
if err != nil {
if os.IsNotExist(err) {
err = ErrMetaNotFound{Resource: name}
@ -66,21 +69,31 @@ func (f *FilesystemStore) SetMultiMeta(metas map[string][]byte) error {
// SetMeta sets the meta for a single role
func (f *FilesystemStore) SetMeta(name string, meta []byte) error {
fileName := fmt.Sprintf("%s.%s", name, f.metaExtension)
path := filepath.Join(f.metaDir, fileName)
fp := f.getPath(name)
// Ensures the parent directories of the file we are about to write exist
err := os.MkdirAll(filepath.Dir(path), 0700)
err := os.MkdirAll(filepath.Dir(fp), 0700)
if err != nil {
return err
}
// if something already exists, just delete it and re-write it
os.RemoveAll(path)
os.RemoveAll(fp)
// Write the file to disk
if err = ioutil.WriteFile(path, meta, 0600); err != nil {
if err = ioutil.WriteFile(fp, meta, 0600); err != nil {
return err
}
return nil
}
// RemoveAll clears the existing filestore by removing its base directory
func (f *FilesystemStore) RemoveAll() error {
return os.RemoveAll(f.baseDir)
}
// RemoveMeta removes the metadata for a single role - if the metadata doesn't
// exist, no error is returned
func (f *FilesystemStore) RemoveMeta(name string) error {
return os.RemoveAll(f.getPath(name)) // RemoveAll succeeds if path doesn't exist
}

View file

@ -85,6 +85,9 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio
if !base.IsAbs() {
return nil, errors.New("HTTPStore requires an absolute baseURL")
}
if roundTrip == nil {
return &OfflineStore{}, nil
}
return &HTTPStore{
baseURL: *base,
metaPrefix: metaPrefix,
@ -182,6 +185,12 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error {
return translateStatusToError(resp, "POST "+name)
}
// RemoveMeta always fails, because we should never be able to delete metadata
// remotely
func (s HTTPStore) RemoveMeta(name string) error {
return ErrInvalidOperation{msg: "cannot delete metadata"}
}
// NewMultiPartMetaRequest builds a request with the provided metadata updates
// in multipart form
func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request, error) {
@ -227,6 +236,11 @@ func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
return translateStatusToError(resp, "POST metadata endpoint")
}
// RemoveAll in the interface is not supported, admins should use the DeleteHandler endpoint directly to delete remote data for a GUN
func (s HTTPStore) RemoveAll() error {
return errors.New("remove all functionality not supported for HTTPStore")
}
func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
var filename string
if name != "" {

View file

@ -14,6 +14,8 @@ type MetadataStore interface {
GetMeta(name string, size int64) ([]byte, error)
SetMeta(name string, blob []byte) error
SetMultiMeta(map[string][]byte) error
RemoveAll() error
RemoveMeta(name string) error
}
// PublicKeyStore must be implemented by a key service

View file

@ -54,6 +54,13 @@ func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error {
return nil
}
// RemoveMeta removes the metadata for a single role - if the metadata doesn't
// exist, no error is returned
func (m *memoryStore) RemoveMeta(name string) error {
delete(m.meta, name)
return nil
}
func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) {
return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil
}
@ -95,3 +102,11 @@ func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) er
func (m *memoryStore) GetKey(role string) ([]byte, error) {
return nil, fmt.Errorf("GetKey is not implemented for the memoryStore")
}
// Clear this existing memory store by setting this store as new empty one
func (m *memoryStore) RemoveAll() error {
m.meta = make(map[string][]byte)
m.files = make(map[string][]byte)
m.keys = make(map[string][]data.PrivateKey)
return nil
}

View file

@ -14,30 +14,40 @@ func (e ErrOffline) Error() string {
var err = ErrOffline{}
// OfflineStore is to be used as a placeholder for a nil store. It simply
// return ErrOffline for every operation
// returns ErrOffline for every operation
type OfflineStore struct{}
// GetMeta return ErrOffline
// GetMeta returns ErrOffline
func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) {
return nil, err
}
// SetMeta return ErrOffline
// SetMeta returns ErrOffline
func (es OfflineStore) SetMeta(name string, blob []byte) error {
return err
}
// SetMultiMeta return ErrOffline
// SetMultiMeta returns ErrOffline
func (es OfflineStore) SetMultiMeta(map[string][]byte) error {
return err
}
// GetKey return ErrOffline
// RemoveMeta returns ErrOffline
func (es OfflineStore) RemoveMeta(name string) error {
return err
}
// GetKey returns ErrOffline
func (es OfflineStore) GetKey(role string) ([]byte, error) {
return nil, err
}
// GetTarget return ErrOffline
// GetTarget returns ErrOffline
func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) {
return nil, err
}
// RemoveAll return ErrOffline
func (es OfflineStore) RemoveAll() error {
return err
}

View file

@ -173,6 +173,11 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
return nil
}
// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
func (tr *Repo) GetAllLoadedRoles() []*data.Role {
return tr.keysDB.GetAllRoles()
}
// GetDelegation finds the role entry representing the provided
// role name or ErrInvalidRole
func (tr *Repo) GetDelegation(role string) (*data.Role, error) {