mirror of
https://github.com/docker/docs.git
synced 2026-11-05 00:14:17 +03:00
Update function description for expected behavior, groundwork for config
in validation Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
@@ -8,9 +8,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// ErrValidationFail is returned when there is no valid trusted certificates
|
||||
@@ -58,25 +60,35 @@ 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 succeeds, or in the case where we found no trusted
|
||||
certificates for this particular GUN, we check the integrity of the root by
|
||||
If the previous validation succeeds, 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
|
||||
themselves.
|
||||
|
||||
If this last steps succeeds, we attempt to do root rotation, by ensuring that
|
||||
we only trust the certificates that are present in the new root.
|
||||
However, if we do not have any current trusted certificates for this GUN, we
|
||||
check if there are any pinned certificates specified in the trust_pinning section
|
||||
of the notary client config. If this section specifies a Certs section with this
|
||||
GUN, we attempt to validate that the certificates present in the downloaded root
|
||||
file match the pinned ID.
|
||||
|
||||
This mechanism of operation is essentially Trust On First Use (TOFU): if we
|
||||
have never seen a certificate for a particular CN, we trust it. If later we see
|
||||
a different certificate for that certificate, we return an ErrValidationFailed error.
|
||||
If the Certs section is empty are empty for this GUN, we check if the trust_pinning
|
||||
section specifies a CA section specified in the config for this GUN. If so, we check
|
||||
that the specified CA is valid and has signed a certificate included in the downloaded
|
||||
root file.
|
||||
|
||||
If both the Certs and CA configs do not match this GUN, we fall back to the TOFU
|
||||
section in the config: if true, we trust certificates specified in the root for
|
||||
this GUN. If later we see a different certificate for that certificate, we return
|
||||
an ErrValidationFailed error.
|
||||
|
||||
Note that since we only allow trust data to be downloaded over an HTTPS channel
|
||||
we are using the current public PKI to validate the first download of the certificate
|
||||
adding an extra layer of security over the normal (SSH style) trust model.
|
||||
We shall call this: TOFUS.
|
||||
|
||||
Validation failure at any step will result in an ErrValidationFailed error.
|
||||
*/
|
||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string) error {
|
||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning notary.TrustPinConfig) error {
|
||||
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||
signedRoot, err := data.RootFromSigned(root)
|
||||
if err != nil {
|
||||
@@ -101,7 +113,6 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
||||
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have certificates that match this specific GUN, let's make sure to
|
||||
// use them first to validate that this new root is valid.
|
||||
if len(trustedCerts) != 0 {
|
||||
@@ -114,9 +125,72 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("found no currently valid root certificates for %s", gun)
|
||||
logrus.Debugf("using trust_pinning config to bootstrap trust: %v", trustPinning)
|
||||
// First, check if the Certs section is specified for our GUN.
|
||||
// If so, we try to find a matching Cert that is pinned to bootstrap trust from
|
||||
if pinnedID, ok := trustPinning.Certs[gun]; ok {
|
||||
foundCertIDMatch := false
|
||||
for _, cert := range certsFromRoot {
|
||||
// Try to match by CertID or public key ID
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if certID == pinnedID {
|
||||
// If we found our pinned cert, only use that one certificate as allValidCerts for verification
|
||||
certsFromRoot = []*x509.Certificate{cert}
|
||||
foundCertIDMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// If we didn't find any entries under our GUN in Certs with a matching certificate ID, fail validation
|
||||
if !foundCertIDMatch {
|
||||
return &ErrValidationFail{Reason: "failed to find matching certificate ID "}
|
||||
}
|
||||
} else if utils.ContainsKeyPrefix(trustPinning.CA, gun) {
|
||||
// Next, check if the CA section is specified with a GUN that prefixes our GUN. If so, we use this CA to bootstrap trust:
|
||||
// We attempt to add the CA PEM if it's valid, and all certs to our certStore that are from this CA
|
||||
if len(trustPinning.CA) > 0 {
|
||||
for caGunPrefix, caFilepath := range trustPinning.CA {
|
||||
if strings.HasPrefix(gun, caGunPrefix) {
|
||||
// Try to add the CA cert to our certificate store,
|
||||
// and use it to validate certs in the root.json later
|
||||
caCert, err := trustmanager.LoadCertFromFile(caFilepath)
|
||||
if err != nil {
|
||||
return &ErrValidationFail{Reason: "failed to load specified CA trust pin"}
|
||||
}
|
||||
if err = trustmanager.ValidateCertificate(caCert); err != nil {
|
||||
return &ErrValidationFail{Reason: "failed to validate specified CA trust pin"}
|
||||
}
|
||||
if err = certStore.AddCertFromFile(caFilepath); err != nil {
|
||||
return &ErrValidationFail{Reason: "failed to add CA trust pin to certificate store"}
|
||||
}
|
||||
opts, err := certStore.GetVerifyOptions(gun)
|
||||
if err != nil {
|
||||
return &ErrValidationFail{Reason: "failed to add CA trust pin to certificate store for verification"}
|
||||
}
|
||||
// Now only consider certificates that are direct children from this CA cert, overwriting allValidCerts
|
||||
validCertsForCA := []*x509.Certificate{}
|
||||
for _, cert := range certsFromRoot {
|
||||
if _, err := cert.Verify(opts); err == nil {
|
||||
validCertsForCA = append(validCertsForCA, cert)
|
||||
}
|
||||
}
|
||||
certsFromRoot = validCertsForCA
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !trustPinning.TOFU {
|
||||
// If we reach this if-case, it means that we didn't find any local certs/CAs for this GUN,
|
||||
// nor did we specify any specifications for how to bootstrap trust in trust_pinning using TOFU
|
||||
// If TOFU is true, we fall through and consider all certificates
|
||||
return &ErrValidationFail{Reason: "could not bootstrap trust without trust_pinning configuration"}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the integrity of the new root (does it have valid signatures)
|
||||
// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
|
||||
// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
|
||||
err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsFromRoot))
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
|
||||
@@ -32,6 +32,10 @@ const validIntermediateAndCertRSA = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUl
|
||||
|
||||
const signedRSARootTemplate = `{"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2016-07-16T23:34:13.389129622-07:00","keys":{"1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nIgzLigo5D47dWQe1IUjzHXxvyx0j/OL16VQymuloWsgVDxxT6+mH3CeviMAs+/McnEPE9exnm6SQGR5x3XMw=="}},"23c29cc372109c819e081bc953b7657d05e3f968f03c21d0d75ea457590f3d14":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEClUFVWkc85OQScfTQRS02VaLIEaeCmxdwYS/hcTLVoTxlFfRfs7HyalTwXGAGO79XZZS+koE6s8D0xGcCJQkLQ=="}},"49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292":{"keytype":"rsa-x509","keyval":{"private":null,"public":"{{.RootPem}}"}},"e3a5a4fdaf11ea1ec58f5efed6f3639b39cd4cfa1418c8b55c9a8c2447ace5d9":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}},"roles":{"root":{"keyids":["49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292"],"threshold":1},"snapshot":{"keyids":["23c29cc372109c819e081bc953b7657d05e3f968f03c21d0d75ea457590f3d14"],"threshold":1},"targets":{"keyids":["1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"],"threshold":1},"timestamp":{"keyids":["e3a5a4fdaf11ea1ec58f5efed6f3639b39cd4cfa1418c8b55c9a8c2447ace5d9"],"threshold":1}},"version":2},"signatures":[{"keyid":"49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292","method":"rsapss","sig":"YlZwtCj028Xc23+KHfj6govFEY6hMbBXO5HT20F0I5ZeIPb1l7OmkjEiwp9ZHusClY+QeqiP1CFh\n/AfCbv4tLanqMkXPtm8UJJ1hMZVq86coieB32PQDj9k6x1hErHzvPUbOzTRW2BQkFFMZFkLDAd06\npH8lmxyPLOhdkVE8qIT7sBCy/4bQIGfvEX6yCDz84MZdcLNX5B9mzGi9A7gDloh9IEZxA8UgoI18\nSYpv/fYeSZSqM/ws2G+kiELGgTWhcZ+gOlF7ArM/DOlcC/NYqcvY1ugE6Gn7G8opre6NOofdRp3w\n603A2rMMvYTwqKLY6oX/d+07A2+WGHXPUy5otCAybWOw2hIZ35Jjmh12g6Dc6Qk4K2zXwAgvWwBU\nWlT8MlP1Tf7f80jnGjh0aARlHI4LCxlYU5L/pCaYuHgynujvLuzoOuiiPfJv7sYvKoQ8UieE1w//\nHc8E6tWtV5G2FguKLurMoKZ9FBWcanDO0fg5AWuG3qcgUJdvh9acQ33EKer1fqBxs6LSAUWo8rDt\nQkg+b55AW0YBukAW9IAfMySQGAS2e3mHZ8nK/ijaygCRu7/P+NgKY9/zpmfL2xgcNslLcANcSOOt\nhiJS6yqYM9i9G0af0yw/TxAT4ntwjVm8u52UyR/hXIiUc/mjZcYRbSmJOHws902+i+Z/qv72knk="}]}`
|
||||
|
||||
const rootPubKeyID = "49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b1f4292"
|
||||
|
||||
const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"
|
||||
|
||||
func TestCertsToRemove(t *testing.T) {
|
||||
// Get a few certificates to test with
|
||||
cert1, err := trustmanager.LoadCertFromFile("../fixtures/secure.example.com.crt")
|
||||
@@ -138,12 +142,12 @@ func TestValidateRoot(t *testing.T) {
|
||||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
// This call to ValidateRoot will fail since we are passing in a dnsName that
|
||||
// doesn't match the CN of the certificate.
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
@@ -157,7 +161,7 @@ func TestValidateRoot(t *testing.T) {
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "illegal base64 data at input byte")
|
||||
|
||||
//
|
||||
@@ -170,7 +174,7 @@ func TestValidateRoot(t *testing.T) {
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
@@ -185,7 +189,7 @@ func TestValidateRoot(t *testing.T) {
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||
|
||||
@@ -203,11 +207,152 @@ func TestValidateRoot(t *testing.T) {
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com")
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "An error was expected")
|
||||
require.Equal(t, err, &ErrValidationFail{Reason: "failed to validate integrity of roots"})
|
||||
}
|
||||
|
||||
func TestValidateRootWithoutTOFUS(t *testing.T) {
|
||||
var testSignedRoot data.Signed
|
||||
var signedRootBytes bytes.Buffer
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{TOFU: false})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestValidateRootWithPinnedCert(t *testing.T) {
|
||||
var testSignedRoot data.Signed
|
||||
var signedRootBytes bytes.Buffer
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID)
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": rootPubKeyID}})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
||||
var testSignedRoot data.Signed
|
||||
var signedRootBytes bytes.Buffer
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot should fail due to an incorrect cert ID
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": "ABSOLUTELY NOT A CERT ID"}})
|
||||
assert.Error(t, err)
|
||||
certStore.RemoveAll()
|
||||
|
||||
// This call to ValidateRoot should fail due to an empty cert ID
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": ""}})
|
||||
assert.Error(t, err)
|
||||
certStore.RemoveAll()
|
||||
|
||||
// This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS defaults to false
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"not_a_gun": rootPubKeyID}, TOFU: false})
|
||||
assert.Error(t, err)
|
||||
certStore.RemoveAll()
|
||||
|
||||
// This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"docker.com/notary": targetsPubKeyID}})
|
||||
assert.Error(t, err)
|
||||
certStore.RemoveAll()
|
||||
|
||||
// This call to ValidateRoot should succeed because we fall through to TOFUS even though we have no matching GUNs under Certs
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{Certs: map[string]string{"not_a_gun": rootPubKeyID}, TOFU: true})
|
||||
assert.NoError(t, err)
|
||||
certStore.RemoveAll()
|
||||
}
|
||||
|
||||
func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||
var testSignedRoot data.Signed
|
||||
var signedRootBytes bytes.Buffer
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
// Create a X509Store
|
||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
trustPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Execute our template
|
||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||
|
||||
// Unmarshal our signedroot
|
||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||
|
||||
// This call to ValidateRoot will fail because we have an invalid path for the CA
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
||||
assert.Error(t, err)
|
||||
|
||||
// This call to ValidateRoot will fail because we have no valid GUNs to use
|
||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", notary.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// TestValidateSuccessfulRootRotation runs through a full root certificate rotation
|
||||
// We test this with both an RSA and ECDSA root certificate
|
||||
func TestValidateSuccessfulRootRotation(t *testing.T) {
|
||||
@@ -296,7 +441,7 @@ func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string
|
||||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun)
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is the new one
|
||||
@@ -357,7 +502,7 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
|
||||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun)
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "insuficient signatures on root")
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is still
|
||||
@@ -421,7 +566,7 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
|
||||
|
||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||
// encoded certificate, and have no other certificates for this CN
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun)
|
||||
err = ValidateRoot(certStore, signedTestRoot, gun, notary.TrustPinConfig{TOFU: true})
|
||||
require.Error(t, err, "insuficient signatures on root")
|
||||
|
||||
// Finally, validate the only trusted certificate that exists is still
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/store"
|
||||
@@ -90,7 +91,7 @@ func Test0Dot1RepoFormat(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(tmpDir, gun, ts.URL, http.DefaultTransport,
|
||||
passphrase.ConstantRetriever(passwd))
|
||||
passphrase.ConstantRetriever(passwd), notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
// targets should have 1 target, and it should be readable offline
|
||||
@@ -152,7 +153,7 @@ func TestDownloading0Dot1RepoFormat(t *testing.T) {
|
||||
defer os.RemoveAll(repoDir)
|
||||
|
||||
repo, err := NewNotaryRepository(repoDir, gun, ts.URL, http.DefaultTransport,
|
||||
passphrase.ConstantRetriever(passwd))
|
||||
passphrase.ConstantRetriever(passwd), notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
err = repo.Update(true)
|
||||
|
||||
@@ -88,13 +88,14 @@ type NotaryRepository struct {
|
||||
tufRepo *tuf.Repo
|
||||
roundTrip http.RoundTripper
|
||||
CertStore trustmanager.X509Store
|
||||
trustPinning notary.TrustPinConfig
|
||||
}
|
||||
|
||||
// repositoryFromKeystores is a helper function for NewNotaryRepository that
|
||||
// takes some basic NotaryRepository parameters as well as keystores (in order
|
||||
// of usage preference), and returns a NotaryRepository.
|
||||
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
keyStores []trustmanager.KeyStore) (*NotaryRepository, error) {
|
||||
keyStores []trustmanager.KeyStore, trustPin notary.TrustPinConfig) (*NotaryRepository, error) {
|
||||
|
||||
certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
@@ -115,6 +116,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
CryptoService: cryptoService,
|
||||
roundTrip: rt,
|
||||
CertStore: certStore,
|
||||
trustPinning: trustPin,
|
||||
}
|
||||
|
||||
fileStore, err := store.NewFilesystemStore(
|
||||
@@ -873,7 +875,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = certs.ValidateRoot(r.CertStore, root, r.gun)
|
||||
err = certs.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ func createRepoAndKey(t *testing.T, rootType, tempBaseDir, gun, url string) (
|
||||
|
||||
rec := newRoleRecorder()
|
||||
repo, err := NewNotaryRepository(
|
||||
tempBaseDir, gun, url, http.DefaultTransport, rec.retriever)
|
||||
tempBaseDir, gun, url, http.DefaultTransport, rec.retriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType)
|
||||
@@ -222,7 +222,7 @@ func newRepoToTestRepo(t *testing.T, existingRepo *NotaryRepository, newDir bool
|
||||
rec := newRoleRecorder()
|
||||
repo, err := NewNotaryRepository(
|
||||
repoDir, existingRepo.gun, existingRepo.baseURL,
|
||||
http.DefaultTransport, rec.retriever)
|
||||
http.DefaultTransport, rec.retriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repository: %s", err)
|
||||
if err != nil && newDir {
|
||||
defer os.RemoveAll(repoDir)
|
||||
@@ -574,7 +574,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) {
|
||||
defer ts.Close()
|
||||
|
||||
retriever := passphrase.ConstantRetriever("password")
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever)
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType)
|
||||
require.NoError(t, err, "error generating root key: %s", err)
|
||||
@@ -582,7 +582,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) {
|
||||
retriever = passphrase.ConstantRetriever("incorrect password")
|
||||
// repo.CryptoService’s FileKeyStore caches the unlocked private key, so to test
|
||||
// private key unlocking we need a new repo instance.
|
||||
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever)
|
||||
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
err = repo.Initialize(rootPubKey.ID())
|
||||
require.EqualError(t, err, trustmanager.ErrAttemptsExceeded{}.Error())
|
||||
@@ -612,14 +612,14 @@ func testInitRepoPasswordInvalid(t *testing.T, rootType string) {
|
||||
defer ts.Close()
|
||||
|
||||
retriever := passphrase.ConstantRetriever("password")
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever)
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
rootPubKey, err := repo.CryptoService.Create("root", repo.gun, rootType)
|
||||
require.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
// repo.CryptoService’s FileKeyStore caches the unlocked private key, so to test
|
||||
// private key unlocking we need a new repo instance.
|
||||
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever)
|
||||
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
err = repo.Initialize(rootPubKey.ID())
|
||||
require.EqualError(t, err, trustmanager.ErrPasswordInvalid{}.Error())
|
||||
@@ -1641,7 +1641,7 @@ func TestPublishUninitializedRepo(t *testing.T) {
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL,
|
||||
http.DefaultTransport, passphraseRetriever)
|
||||
http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repository: %s", err)
|
||||
err = repo.Publish()
|
||||
require.Error(t, err)
|
||||
@@ -2005,7 +2005,7 @@ func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(
|
||||
tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever)
|
||||
tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphraseRetriever))
|
||||
@@ -2863,7 +2863,7 @@ func TestRemoteServerUnavailableNoLocalCache(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, "docker.com/notary",
|
||||
ts.URL, http.DefaultTransport, passphraseRetriever)
|
||||
ts.URL, http.DefaultTransport, passphraseRetriever, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
_, err = repo.ListTargets(data.CanonicalTargetsRole)
|
||||
@@ -3173,6 +3173,7 @@ func TestBootstrapClientBadURL(t *testing.T) {
|
||||
"http://localhost:9998",
|
||||
http.DefaultTransport,
|
||||
passphraseRetriever,
|
||||
notary.TrustPinConfig{TOFU: true},
|
||||
)
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
@@ -3202,6 +3203,7 @@ func TestBootstrapClientInvalidURL(t *testing.T) {
|
||||
"#!*)&!)#*^%!#)%^!#",
|
||||
http.DefaultTransport,
|
||||
passphraseRetriever,
|
||||
notary.TrustPinConfig{TOFU: true},
|
||||
)
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/certs"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/client"
|
||||
@@ -31,7 +32,7 @@ func newBlankRepo(t *testing.T, url string) *NotaryRepository {
|
||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||
|
||||
repo, err := NewNotaryRepository(tempBaseDir, "docker.com/notary", url,
|
||||
http.DefaultTransport, passphrase.ConstantRetriever("pass"))
|
||||
http.DefaultTransport, passphrase.ConstantRetriever("pass"), notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err)
|
||||
return repo
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
)
|
||||
@@ -14,7 +15,7 @@ import (
|
||||
// It takes the base directory under where all the trust files will be stored
|
||||
// (usually ~/.docker/trust/).
|
||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
retriever passphrase.Retriever) (
|
||||
retriever passphrase.Retriever, trustPinning notary.TrustPinConfig) (
|
||||
*NotaryRepository, error) {
|
||||
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||
@@ -23,5 +24,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
}
|
||||
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt,
|
||||
[]trustmanager.KeyStore{fileKeyStore})
|
||||
[]trustmanager.KeyStore{fileKeyStore}, trustPinning)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustmanager/yubikey"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
// It takes the base directory under where all the trust files will be stored
|
||||
// (usually ~/.docker/trust/).
|
||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
retriever passphrase.Retriever) (
|
||||
retriever passphrase.Retriever, trustPinning notary.TrustPinConfig) (
|
||||
*NotaryRepository, error) {
|
||||
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||
@@ -29,5 +30,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
|
||||
}
|
||||
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores)
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores, trustPinning)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func (c *certCommander) certRemove(cmd *cobra.Command, args []string) error {
|
||||
// Remove all TUF data, so call RemoveTrustData on a NotaryRepository with the GUN
|
||||
// no online operations are performed so the transport argument is nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever)
|
||||
trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not establish trust data for GUN %s", c.certRemoveGUN)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/docker/notary"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
@@ -85,7 +86,7 @@ func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string)
|
||||
|
||||
// initialize repo with transport to get latest state of the world before listing delegations
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,7 +141,7 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -246,7 +247,7 @@ func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) e
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/spf13/viper"
|
||||
@@ -21,6 +22,7 @@ func setup() *delegationCommander {
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
mainViper := viper.New()
|
||||
mainViper.Set("trust_dir", testTrustDir)
|
||||
mainViper.Set("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return mainViper, nil
|
||||
},
|
||||
retriever: nil,
|
||||
|
||||
@@ -420,7 +420,7 @@ func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error {
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config),
|
||||
rt, k.getRetriever())
|
||||
rt, k.getRetriever(), config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -249,7 +249,11 @@ func TestRotateKeyInvalidRoles(t *testing.T) {
|
||||
for _, role := range invalids {
|
||||
for _, serverManaged := range []bool{true, false} {
|
||||
k := &keyCommander{
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return v, nil
|
||||
},
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: role,
|
||||
rotateKeyServerManaged: serverManaged,
|
||||
@@ -270,7 +274,11 @@ func TestRotateKeyInvalidRoles(t *testing.T) {
|
||||
func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) {
|
||||
setUp(t)
|
||||
k := &keyCommander{
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return v, nil
|
||||
},
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: data.CanonicalTargetsRole,
|
||||
rotateKeyServerManaged: true,
|
||||
@@ -284,7 +292,11 @@ func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) {
|
||||
func TestRotateKeyTimestampCannotBeLocallyManaged(t *testing.T) {
|
||||
setUp(t)
|
||||
k := &keyCommander{
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return v, nil
|
||||
},
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: data.CanonicalTimestampRole,
|
||||
rotateKeyServerManaged: false,
|
||||
@@ -329,7 +341,7 @@ func setUpRepo(t *testing.T, tempBaseDir, gun string, ret passphrase.Retriever)
|
||||
ts := httptest.NewServer(server.RootHandler(nil, ctx, cryptoService, nil, nil))
|
||||
|
||||
repo, err := client.NewNotaryRepository(
|
||||
tempBaseDir, gun, ts.URL, http.DefaultTransport, ret)
|
||||
tempBaseDir, gun, ts.URL, http.DefaultTransport, ret, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootPubKey, err := repo.CryptoService.Create("root", "", data.ECDSAKey)
|
||||
@@ -364,6 +376,7 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) {
|
||||
v := viper.New()
|
||||
v.SetDefault("trust_dir", tempBaseDir)
|
||||
v.SetDefault("remote_server.url", ts.URL)
|
||||
v.SetDefault("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return v, nil
|
||||
},
|
||||
getRetriever: func() passphrase.Retriever { return ret },
|
||||
@@ -371,7 +384,7 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, role, "-r"}))
|
||||
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, ret)
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, ret, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
cl, err := repo.GetChangelist()
|
||||
@@ -395,7 +408,6 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,6 +431,7 @@ func TestRotateKeyBothKeys(t *testing.T) {
|
||||
v := viper.New()
|
||||
v.SetDefault("trust_dir", tempBaseDir)
|
||||
v.SetDefault("remote_server.url", ts.URL)
|
||||
v.Set("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
return v, nil
|
||||
},
|
||||
getRetriever: func() passphrase.Retriever { return ret },
|
||||
@@ -426,7 +439,7 @@ func TestRotateKeyBothKeys(t *testing.T) {
|
||||
require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, data.CanonicalTargetsRole}))
|
||||
require.NoError(t, k.keysRotate(&cobra.Command{}, []string{gun, data.CanonicalSnapshotRole}))
|
||||
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, nil, ret)
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, nil, ret, notary.TrustPinConfig{TOFU: true})
|
||||
require.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
cl, err := repo.GetChangelist()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/version"
|
||||
@@ -97,6 +98,7 @@ func (n *notaryCommander) parseConfig() (*viper.Viper, error) {
|
||||
// Setup the configuration details into viper
|
||||
config.SetDefault("trust_dir", defaultTrustDir)
|
||||
config.SetDefault("remote_server", map[string]string{"url": defaultServerURL})
|
||||
config.SetDefault("trust_pinning", notary.TrustPinConfig{TOFU: true})
|
||||
|
||||
// Find and read the config file
|
||||
if err := config.ReadInConfig(); err != nil {
|
||||
@@ -109,6 +111,13 @@ func (n *notaryCommander) parseConfig() (*viper.Viper, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the trust_pinning section was a valid notary.TrustPinConfig object
|
||||
// We parse this here so that parsing and usage are separated in code, we then use type assertions later
|
||||
if _, ok := config.Get("trust_pinning").(notary.TrustPinConfig); !ok {
|
||||
// Return a noisy error if the user passed an invalid trust_pinning section instead of sticking to default TOFUS
|
||||
return nil, fmt.Errorf("error parsing trust_pinning section of config")
|
||||
}
|
||||
|
||||
// At this point we either have the default value or the one set by the config.
|
||||
// Either way, some command-line flags have precedence and overwrites the value
|
||||
if n.trustDir != "" {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
@@ -119,7 +120,7 @@ func (t *tufCommander) tufAdd(cmd *cobra.Command, args []string) error {
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -156,7 +157,7 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,7 +202,7 @@ func (t *tufCommander) tufList(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -236,7 +237,7 @@ func (t *tufCommander) tufLookup(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -263,7 +264,7 @@ func (t *tufCommander) tufStatus(cmd *cobra.Command, args []string) error {
|
||||
gun := args[0]
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -307,7 +308,7 @@ func (t *tufCommander) tufPublish(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -333,7 +334,7 @@ func (t *tufCommander) tufRemove(cmd *cobra.Command, args []string) error {
|
||||
// no online operation are performed by remove so the transport argument
|
||||
// should be nil.
|
||||
repo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -372,7 +373,7 @@ func (t *tufCommander) tufVerify(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever, config.Get("trust_pinning").(notary.TrustPinConfig))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
8
const.go
8
const.go
@@ -64,3 +64,11 @@ var NotaryDefaultExpiries = map[string]time.Duration{
|
||||
"snapshot": NotarySnapshotExpiry,
|
||||
"timestamp": NotaryTimestampExpiry,
|
||||
}
|
||||
|
||||
// TrustPinConfig represents the configuration under the trust_pinning section of the config file
|
||||
// This struct represents the preferred way to bootstrap trust for this repository
|
||||
type TrustPinConfig struct {
|
||||
CA map[string]string
|
||||
Certs map[string]string
|
||||
TOFU bool
|
||||
}
|
||||
|
||||
@@ -67,6 +67,16 @@ func StrSliceContainsI(ss []string, s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsKeyPrefix checks if the given string map contains a key that prefixes the desired key
|
||||
func ContainsKeyPrefix(smap map[string]string, s string) bool {
|
||||
for k := range smap {
|
||||
if strings.HasPrefix(s, k) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FileExists returns true if a file (or dir) exists at the given path,
|
||||
// false otherwise
|
||||
func FileExists(path string) bool {
|
||||
|
||||
Reference in New Issue
Block a user