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" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION docker-v1.10-3 ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && 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" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_COMMIT 0c11a970826e62479379ccc75a45184460b9200f ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && 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" \ && 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 \ go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"

View File

@ -144,7 +144,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION docker-v1.10-2 ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && 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+ # TODO update this when we upgrade to Go 1.5.1+
# Install notary server # Install notary server
#ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7 #ENV NOTARY_VERSION docker-v1.10-4
#RUN set -x \ #RUN set -x \
# && export GOPATH="$(mktemp -d)" \ # && export GOPATH="$(mktemp -d)" \
# && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ # && 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" \ # && 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 \ # go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
# && rm -rf "$GOPATH" # && rm -rf "$GOPATH"

View File

@ -116,11 +116,11 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7 ENV NOTARY_VERSION docker-v1.10-4
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && 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" \ && 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 \ go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
&& rm -rf "$GOPATH" && 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 clone git github.com/vbatts/tar-split v0.9.11
# get desired notary commit, might also need to be updated in Dockerfile # 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 google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf 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/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
clone git github.com/opencontainers/runc 3d8a20bb772defc28c355534d83486416d1719b4 # libcontainer 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) { 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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", withTagName)
dockerCmd(c, "push", repoName) dockerCmd(c, "push", withTagName)
dockerCmd(c, "rmi", repoName) dockerCmd(c, "rmi", withTagName)
// Try trusted create on untrusted tag // Try trusted create on untrusted tag
createCmd := exec.Command(dockerBinary, "create", repoName) createCmd := exec.Command(dockerBinary, "create", withTagName)
s.trustedCmd(createCmd) s.trustedCmd(createCmd)
out, _, err := runCommandWithOutput(createCmd) out, _, err := runCommandWithOutput(createCmd)
c.Assert(err, check.Not(check.IsNil)) 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) { 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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
dockerCmd(c, "push", repoName) dockerCmd(c, "push", repoName)

View File

@ -215,7 +215,7 @@ func (s *DockerSchema1RegistrySuite) TestCrossRepositoryLayerPushNotSupported(c
} }
func (s *DockerTrustSuite) TestTrustedPush(c *check.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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
@ -267,7 +267,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithDeprecatedEnvPasswords(c *check.C)
} }
func (s *DockerTrustSuite) TestTrustedPushWithFailingServer(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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
@ -279,7 +279,7 @@ func (s *DockerTrustSuite) TestTrustedPushWithFailingServer(c *check.C) {
} }
func (s *DockerTrustSuite) TestTrustedPushWithoutServerAndUntrusted(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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) 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) { func (s *DockerTrustSuite) TestUntrustedRun(c *check.C) {
// Windows does not support this functionality // Windows does not support this functionality
testRequires(c, DaemonIsLinux) 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 // tag the image and upload it to the private registry
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
dockerCmd(c, "push", repoName) dockerCmd(c, "push", repoName)

View File

@ -118,12 +118,13 @@ protos:
# be run first # be run first
define gocover 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 endef
gen-cover: go_version gen-cover: go_version
@mkdir -p "$(COVERDIR)" @mkdir -p "$(COVERDIR)"
$(foreach PKG,$(PKGS),$(call gocover,$(PKG))) $(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 # Generates the cover binaries and runs them all in serial, so this can be used
# run all tests with a yubikey without any problems # run all tests with a yubikey without any problems

View File

@ -4,7 +4,6 @@ import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"path/filepath"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
@ -13,14 +12,6 @@ import (
"github.com/docker/notary/tuf/signed" "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 // ErrValidationFail is returned when there is no valid trusted certificates
// being served inside of the roots.json // being served inside of the roots.json
type ErrValidationFail struct { 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) 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 ValidateRoot receives a new root, validates its correctness and attempts to
do root key rotation if needed. 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 have a list of trusted certificates for it. In this case, we use this list of
certificates to attempt to validate this root file. 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 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 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 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. adding an extra layer of security over the normal (SSH style) trust model.
We shall call this: TOFUS. 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) logrus.Debugf("entered ValidateRoot with dns: %s", gun)
signedRoot, err := data.RootFromSigned(root) signedRoot, err := data.RootFromSigned(root)
if err != nil { 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 // Retrieve all the trusted certificates that match this gun
certsForCN, err := m.trustedCertificateStore.GetCertificatesByCN(gun) certsForCN, err := certStore.GetCertificatesByCN(gun)
if err != nil { if err != nil {
// If the error that we get back is different than ErrNoCertificatesFound // If the error that we get back is different than ErrNoCertificatesFound
// we couldn't check if there are any certificates with this CN already // 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 // 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) // First we add all the new certificates (even if they already exist)
for _, cert := range allValidCerts { for _, cert := range allValidCerts {
err := m.trustedCertificateStore.AddCert(cert) err := certStore.AddCert(cert)
if err != nil { if err != nil {
// If the error is already exists we don't fail the rotation // If the error is already exists we don't fail the rotation
if _, ok := err.(*trustmanager.ErrCertExists); ok { 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 // Now we delete old certificates that aren't present in the new root
for certID, cert := range certsToRemove(certsForCN, allValidCerts) { for certID, cert := range certsToRemove(certsForCN, allValidCerts) {
logrus.Debugf("removing certificate with certID: %s", certID) logrus.Debugf("removing certificate with certID: %s", certID)
err = m.trustedCertificateStore.RemoveCert(cert) err = certStore.RemoveCert(cert)
if err != nil { if err != nil {
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err) logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
return &ErrRootRotationFail{Reason: "failed to rotate root keys"} return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
@ -208,7 +142,7 @@ func (m *Manager) ValidateRoot(root *data.Signed, gun string) error {
return nil 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 // Common-Names match the provided GUN
func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) { func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) {
// Get a list of all of the leaf certificates present in root // 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 { for _, cert := range allLeafCerts {
// Validate that this leaf certificate has a CN that matches the exact gun // Validate that this leaf certificate has a CN that matches the exact gun
if cert.Subject.CommonName != 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 continue
} }
// Make sure the certificate is not expired // Make sure the certificate is not expired

View File

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

View File

@ -9,10 +9,10 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary"
"github.com/docker/notary/certs" "github.com/docker/notary/certs"
"github.com/docker/notary/client/changelist" "github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
@ -53,9 +53,9 @@ type ErrInvalidRemoteRole struct {
Role string Role string
} }
func (e ErrInvalidRemoteRole) Error() string { func (err ErrInvalidRemoteRole) Error() string {
return fmt.Sprintf( 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 // ErrRepositoryNotExist is returned when an action is taken on a remote
@ -84,7 +84,7 @@ type NotaryRepository struct {
CryptoService signed.CryptoService CryptoService signed.CryptoService
tufRepo *tuf.Repo tufRepo *tuf.Repo
roundTrip http.RoundTripper roundTrip http.RoundTripper
CertManager *certs.Manager CertStore trustmanager.X509Store
} }
// repositoryFromKeystores is a helper function for NewNotaryRepository that // repositoryFromKeystores is a helper function for NewNotaryRepository that
@ -93,7 +93,11 @@ type NotaryRepository struct {
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
keyStores []trustmanager.KeyStore) (*NotaryRepository, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -107,7 +111,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)), tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
CryptoService: cryptoService, CryptoService: cryptoService,
roundTrip: rt, roundTrip: rt,
CertManager: certManager, CertStore: certStore,
} }
fileStore, err := store.NewFilesystemStore( 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 // currently we only support server managing timestamps and snapshots, and
// nothing else - timestamps are always managed by the server, and implicit // nothing else - timestamps are always managed by the server, and implicit
// (do not have to be passed in as part of `serverManagedRoles`, so that // (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 var serverManagesSnapshot bool
locallyManagedKeys := []string{ locallyManagedKeys := []string{
data.CanonicalTargetsRole, data.CanonicalTargetsRole,
@ -197,7 +201,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
if err != nil { if err != nil {
return err return err
} }
r.CertManager.AddTrustedCert(rootCert) r.CertStore.AddCert(rootCert)
// The root key gets stored in the TUF metadata X509 encoded, linking // The root key gets stored in the TUF metadata X509 encoded, linking
// the tuf root.json to our X509 PKI. // 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 var changes []changelist.Change
for _, role := range roles { for _, role := range roles {
role = strings.ToLower(role)
// Ensure we can only add targets to the CanonicalTargetsRole, // Ensure we can only add targets to the CanonicalTargetsRole,
// or a Delegation role (which is <CanonicalTargetsRole>/something else) // or a Delegation role (which is <CanonicalTargetsRole>/something else)
if role != data.CanonicalTargetsRole && !data.IsDelegation(role) { 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. // the repository when the changelist gets applied at publish time.
// This does not validate that the delegation exists, since one might exist // This does not validate that the delegation exists, since one might exist
// after applying all changes. // 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) { if !data.IsDelegation(name) {
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role 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() defer cl.Close()
logrus.Debugf(`Removing delegation "%s"\n`, name) logrus.Debugf(`Removing delegation "%s"\n`, name)
var template *changelist.TufChange
template := changelist.NewTufChange( // 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, changelist.ActionDelete,
name, name,
changelist.TypeTargetsDelegation, changelist.TypeTargetsDelegation,
"", // no path "", // no path
nil, 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) return addChange(cl, template, name)
} }
// AddTarget creates new changelist entries to add a target to the given roles // 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". // If roles are unspecified, the default role is "targets".
func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error { 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 { for _, role := range roles {
// we don't need to do anything special with removing role from // we don't need to do anything special with removing role from
// roles because listSubtree always processes role and only excludes // 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...) r.listSubtree(targets, role, roles...)
} }
@ -509,6 +532,92 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
return cl, nil 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 // Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase` // Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) Publish() error { func (r *NotaryRepository) Publish() error {
@ -837,7 +946,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
return nil, err return nil, err
} }
err = r.CertManager.ValidateRoot(root, r.gun) err = certs.ValidateRoot(r.CertStore, root, r.gun)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -904,3 +1013,27 @@ func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.Publi
} }
return nil 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" "fmt"
"net/http" "net/http"
"path" "path"
"strings"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
@ -85,13 +86,13 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
return err return err
} }
if err == nil { if err == nil {
// role existed // role existed, attempt to merge paths and keys
return data.ErrInvalidRole{ if err := r.AddPaths(td.AddPaths); err != nil {
Role: c.Scope(), return err
Reason: "cannot create a role that already exists",
} }
return repo.UpdateDelegations(r, td.AddKeys)
} }
// role doesn't exist, create brand new // create brand new role
r, err = td.ToNewRole(c.Scope()) r, err = td.ToNewRole(c.Scope())
if err != nil { if err != nil {
return err return err
@ -107,7 +108,12 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
if err != nil { if err != nil {
return err 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 { if err := r.AddPaths(td.AddPaths); err != nil {
return err return err
} }

View File

@ -2,6 +2,14 @@ package notary
// application wide constants // application wide constants
const ( 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 PrivKeyPerms = 0700
// PubCertPerms are the file permissions to use when writing public certificates to disk
PubCertPerms = 0755 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 { if err != nil {
return nil, fmt.Errorf("failed to add key to filestore: %v", err) 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. // 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 // GetIntermediateCerts parses a list of x509 Certificates and returns all of the
// ones marked as a CA, to be used as intermediates // 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 { for _, cert := range certs {
if cert.IsCA { if cert.IsCA {
intCerts = append(intCerts, cert) 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 // GenerateRSAKey generates an RSA private key and returns a TUF PrivateKey
func GenerateRSAKey(random io.Reader, bits int) (data.PrivateKey, error) { func GenerateRSAKey(random io.Reader, bits int) (data.PrivateKey, error) {
rsaPrivKey, err := rsa.GenerateKey(random, bits) rsaPrivKey, err := rsa.GenerateKey(random, bits)
@ -532,3 +571,14 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
return key.ID(), nil 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 { if err != nil {
logrus.Debug("Error occurred. Root will be downloaded and another update attempted") logrus.Debug("Error occurred. Root will be downloaded and another update attempted")
if err := c.downloadRoot(); err != nil { if err := c.downloadRoot(); err != nil {
logrus.Error("client Update (Root):", err) logrus.Error("Client Update (Root):", err)
return err return err
} }
// If we error again, we now have the latest root and just want to fail // 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 may not have a cached timestamp if this is the first time
// we're interacting with the repo. This will result in the // we're interacting with the repo. This will result in the
// version being 0 // version being 0
var download bool var (
old := &data.Signed{} saveToCache bool
version := 0 old *data.Signed
version = 0
)
cachedTS, err := c.cache.GetMeta(role, maxSize) cachedTS, err := c.cache.GetMeta(role, maxSize)
if err == nil { if err == nil {
err := json.Unmarshal(cachedTS, old) cached := &data.Signed{}
err := json.Unmarshal(cachedTS, cached)
if err == nil { if err == nil {
ts, err := data.TimestampFromSigned(old) ts, err := data.TimestampFromSigned(cached)
if err == nil { if err == nil {
version = ts.Signed.Version version = ts.Signed.Version
} }
} else { old = cached
old = nil
} }
} }
// unlike root, targets and snapshot, always try and download timestamps // unlike root, targets and snapshot, always try and download timestamps
// from remote, only using the cache one if we couldn't reach remote. // from remote, only using the cache one if we couldn't reach remote.
raw, s, err := c.downloadSigned(role, maxSize, nil) raw, s, err := c.downloadSigned(role, maxSize, nil)
if err != nil || len(raw) == 0 { if err != nil || len(raw) == 0 {
if err, ok := err.(store.ErrMetaNotFound); ok {
return err
}
if old == nil { if old == nil {
if err == nil { if err == nil {
// couldn't retrieve data from server and don't have valid // couldn't retrieve data from server and don't have valid
@ -277,17 +276,18 @@ func (c *Client) downloadTimestamp() error {
} }
return err 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 s = old
} else { } else {
download = true saveToCache = true
} }
err = signed.Verify(s, role, version, c.keysDB) err = signed.Verify(s, role, version, c.keysDB)
if err != nil { if err != nil {
return err return err
} }
logrus.Debug("successfully verified timestamp") logrus.Debug("successfully verified timestamp")
if download { if saveToCache {
c.cache.SetMeta(role, raw) c.cache.SetMeta(role, raw)
} }
ts, err := data.TimestampFromSigned(s) ts, err := data.TimestampFromSigned(s)
@ -327,7 +327,7 @@ func (c *Client) downloadSnapshot() error {
} }
err := json.Unmarshal(raw, old) err := json.Unmarshal(raw, old)
if err == nil { if err == nil {
snap, err := data.TimestampFromSigned(old) snap, err := data.SnapshotFromSigned(old)
if err == nil { if err == nil {
version = snap.Signed.Version version = snap.Signed.Version
} else { } else {

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package data 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 // Serializer is an interface that can marshal and unmarshal TUF data. This
// is expected to be a canonical JSON marshaller // is expected to be a canonical JSON marshaller

View File

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

View File

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

View File

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

View File

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

View File

@ -58,6 +58,15 @@ func (db *KeyDB) AddRole(r *data.Role) error {
return nil 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 // GetKey pulls a key out of the database by its ID
func (db *KeyDB) GetKey(id string) data.PublicKey { func (db *KeyDB) GetKey(id string) data.PublicKey {
return db.keys[id] return db.keys[id]

View File

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

View File

@ -39,11 +39,14 @@ type FilesystemStore struct {
targetsDir string 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) // GetMeta returns the meta for the given name (a role)
func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) { func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
fileName := fmt.Sprintf("%s.%s", name, f.metaExtension) meta, err := ioutil.ReadFile(f.getPath(name))
path := filepath.Join(f.metaDir, fileName)
meta, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = ErrMetaNotFound{Resource: name} 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 // SetMeta sets the meta for a single role
func (f *FilesystemStore) SetMeta(name string, meta []byte) error { func (f *FilesystemStore) SetMeta(name string, meta []byte) error {
fileName := fmt.Sprintf("%s.%s", name, f.metaExtension) fp := f.getPath(name)
path := filepath.Join(f.metaDir, fileName)
// Ensures the parent directories of the file we are about to write exist // 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 { if err != nil {
return err return err
} }
// if something already exists, just delete it and re-write it // if something already exists, just delete it and re-write it
os.RemoveAll(path) os.RemoveAll(fp)
// Write the file to disk // 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 err
} }
return nil 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() { if !base.IsAbs() {
return nil, errors.New("HTTPStore requires an absolute baseURL") return nil, errors.New("HTTPStore requires an absolute baseURL")
} }
if roundTrip == nil {
return &OfflineStore{}, nil
}
return &HTTPStore{ return &HTTPStore{
baseURL: *base, baseURL: *base,
metaPrefix: metaPrefix, metaPrefix: metaPrefix,
@ -182,6 +185,12 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error {
return translateStatusToError(resp, "POST "+name) 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 // NewMultiPartMetaRequest builds a request with the provided metadata updates
// in multipart form // in multipart form
func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request, error) { 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") 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) { func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
var filename string var filename string
if name != "" { if name != "" {

View File

@ -14,6 +14,8 @@ type MetadataStore interface {
GetMeta(name string, size int64) ([]byte, error) GetMeta(name string, size int64) ([]byte, error)
SetMeta(name string, blob []byte) error SetMeta(name string, blob []byte) error
SetMultiMeta(map[string][]byte) error SetMultiMeta(map[string][]byte) error
RemoveAll() error
RemoveMeta(name string) error
} }
// PublicKeyStore must be implemented by a key service // 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 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) { func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) {
return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil 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) { func (m *memoryStore) GetKey(role string) ([]byte, error) {
return nil, fmt.Errorf("GetKey is not implemented for the memoryStore") 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{} var err = ErrOffline{}
// OfflineStore is to be used as a placeholder for a nil store. It simply // 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{} type OfflineStore struct{}
// GetMeta return ErrOffline // GetMeta returns ErrOffline
func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) { func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) {
return nil, err return nil, err
} }
// SetMeta return ErrOffline // SetMeta returns ErrOffline
func (es OfflineStore) SetMeta(name string, blob []byte) error { func (es OfflineStore) SetMeta(name string, blob []byte) error {
return err return err
} }
// SetMultiMeta return ErrOffline // SetMultiMeta returns ErrOffline
func (es OfflineStore) SetMultiMeta(map[string][]byte) error { func (es OfflineStore) SetMultiMeta(map[string][]byte) error {
return err 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) { func (es OfflineStore) GetKey(role string) ([]byte, error) {
return nil, err return nil, err
} }
// GetTarget return ErrOffline // GetTarget returns ErrOffline
func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) { func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) {
return nil, err 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 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 // GetDelegation finds the role entry representing the provided
// role name or ErrInvalidRole // role name or ErrInvalidRole
func (tr *Repo) GetDelegation(role string) (*data.Role, error) { func (tr *Repo) GetDelegation(role string) (*data.Role, error) {