2017-09-26 20:16:18 -04:00
|
|
|
package trust
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/pem"
|
|
|
|
"fmt"
|
2022-02-25 08:33:57 -05:00
|
|
|
"io"
|
2017-09-26 20:16:18 -04:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-02-27 10:54:36 -05:00
|
|
|
"runtime"
|
2017-09-26 20:16:18 -04:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/docker/cli/cli/config"
|
|
|
|
"github.com/docker/cli/internal/test"
|
2017-10-30 12:21:41 -04:00
|
|
|
"github.com/theupdateframework/notary"
|
|
|
|
"github.com/theupdateframework/notary/passphrase"
|
|
|
|
"github.com/theupdateframework/notary/storage"
|
|
|
|
"github.com/theupdateframework/notary/trustmanager"
|
|
|
|
tufutils "github.com/theupdateframework/notary/tuf/utils"
|
2020-02-22 12:12:14 -05:00
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
|
|
|
"gotest.tools/v3/skip"
|
2017-09-26 20:16:18 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestTrustKeyLoadErrors(t *testing.T) {
|
2018-02-27 10:54:36 -05:00
|
|
|
noSuchFile := "stat iamnotakey: no such file or directory"
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
noSuchFile = "CreateFile iamnotakey: The system cannot find the file specified."
|
|
|
|
}
|
2017-09-26 20:16:18 -04:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
args []string
|
|
|
|
expectedError string
|
|
|
|
expectedOutput string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "not-enough-args",
|
|
|
|
expectedError: "exactly 1 argument",
|
|
|
|
expectedOutput: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "too-many-args",
|
|
|
|
args: []string{"iamnotakey", "alsonotakey"},
|
|
|
|
expectedError: "exactly 1 argument",
|
|
|
|
expectedOutput: "",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "not-a-key",
|
|
|
|
args: []string{"iamnotakey"},
|
2018-02-27 10:54:36 -05:00
|
|
|
expectedError: "refusing to load key from iamnotakey: " + noSuchFile,
|
2017-10-25 13:45:10 -04:00
|
|
|
expectedOutput: "Loading key from \"iamnotakey\"...\n",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad-key-name",
|
|
|
|
args: []string{"iamnotakey", "--name", "KEYNAME"},
|
|
|
|
expectedError: "key name \"KEYNAME\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character",
|
|
|
|
expectedOutput: "",
|
2017-09-26 20:16:18 -04:00
|
|
|
},
|
|
|
|
}
|
2022-02-25 08:33:57 -05:00
|
|
|
config.SetDir(t.TempDir())
|
2017-09-26 20:16:18 -04:00
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
cli := test.NewFakeCli(&fakeClient{})
|
|
|
|
cmd := newKeyLoadCommand(cli)
|
|
|
|
cmd.SetArgs(tc.args)
|
2022-02-25 08:33:57 -05:00
|
|
|
cmd.SetOut(io.Discard)
|
test spring-cleaning
This makes a quick pass through our tests;
Discard output/err
----------------------------------------------
Many tests were testing for error-conditions, but didn't discard output.
This produced a lot of noise when running the tests, and made it hard
to discover if there were actual failures, or if the output was expected.
For example:
=== RUN TestConfigCreateErrors
Error: "create" requires exactly 2 arguments.
See 'create --help'.
Usage: create [OPTIONS] CONFIG file|- [flags]
Create a config from a file or STDIN
Error: "create" requires exactly 2 arguments.
See 'create --help'.
Usage: create [OPTIONS] CONFIG file|- [flags]
Create a config from a file or STDIN
Error: error creating config
--- PASS: TestConfigCreateErrors (0.00s)
And after discarding output:
=== RUN TestConfigCreateErrors
--- PASS: TestConfigCreateErrors (0.00s)
Use sub-tests where possible
----------------------------------------------
Some tests were already set-up to use test-tables, and even had a usable
name (or in some cases "error" to check for). Change them to actual sub-
tests. Same test as above, but now with sub-tests and output discarded:
=== RUN TestConfigCreateErrors
=== RUN TestConfigCreateErrors/requires_exactly_2_arguments
=== RUN TestConfigCreateErrors/requires_exactly_2_arguments#01
=== RUN TestConfigCreateErrors/error_creating_config
--- PASS: TestConfigCreateErrors (0.00s)
--- PASS: TestConfigCreateErrors/requires_exactly_2_arguments (0.00s)
--- PASS: TestConfigCreateErrors/requires_exactly_2_arguments#01 (0.00s)
--- PASS: TestConfigCreateErrors/error_creating_config (0.00s)
PASS
It's not perfect in all cases (in the above, there's duplicate "expected"
errors, but Go conveniently adds "#01" for the duplicate). There's probably
also various tests I missed that could still use the same changes applied;
we can improve these in follow-ups.
Set cmd.Args to prevent test-failures
----------------------------------------------
When running tests from my IDE, it compiles the tests before running,
then executes the compiled binary to run the tests. Cobra doesn't like
that, because in that situation `os.Args` is taken as argument for the
command that's executed. The command that's tested now sees the test-
flags as arguments (`-test.v -test.run ..`), which causes various tests
to fail ("Command XYZ does not accept arguments").
# compile the tests:
go test -c -o foo.test
# execute the test:
./foo.test -test.v -test.run TestFoo
=== RUN TestFoo
Error: "foo" accepts no arguments.
The Cobra maintainers ran into the same situation, and for their own
use have added a special case to ignore `os.Args` in these cases;
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L1078-L1083
args := c.args
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
args = os.Args[1:]
}
Unfortunately, that exception is too specific (only checks for `cobra.test`),
so doesn't automatically fix the issue for other test-binaries. They did
provide a `cmd.SetArgs()` utility for this purpose
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L276-L280
// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
// particularly useful when testing.
func (c *Command) SetArgs(a []string) {
c.args = a
}
And the fix is to explicitly set the command's args to an empty slice to
prevent Cobra from falling back to using `os.Args[1:]` as arguments.
cmd := newSomeThingCommand()
cmd.SetArgs([]string{})
Some tests already take this issue into account, and I updated some tests
for this, but there's likely many other ones that can use the same treatment.
Perhaps the Cobra maintainers would accept a contribution to make their
condition less specific and to look for binaries ending with a `.test`
suffix (which is what compiled binaries usually are named as).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ab230240ad44fdffa03558a3dbb47971f6336911)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-03 19:29:04 -04:00
|
|
|
cmd.SetErr(io.Discard)
|
2018-03-06 14:03:47 -05:00
|
|
|
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Contains(cli.OutBuffer().String(), tc.expectedOutput))
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
var rsaPrivKeyFixture = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
2017-09-26 20:16:18 -04:00
|
|
|
MIIEpAIBAAKCAQEAs7yVMzCw8CBZPoN+QLdx3ZzbVaHnouHIKu+ynX60IZ3stpbb
|
|
|
|
6rowu78OWON252JcYJqe++2GmdIgbBhg+mZDwhX0ZibMVztJaZFsYL+Ch/2J9KqD
|
|
|
|
A5NtE1s/XdhYoX5hsv7W4ok9jLFXRYIMj+T4exJRlR4f4GP9p0fcqPWd9/enPnlJ
|
|
|
|
JFTmu0DXJTZUMVS1UrXUy5t/DPXdrwyl8pM7VCqO3bqK7jqE6mWawdTkEeiku1fJ
|
|
|
|
ydP0285uiYTbj1Q38VVhPwXzMuLbkaUgRJhCI4BcjfQIjtJLbWpS+VdhUEvtgMVx
|
|
|
|
XJMKxCVGG69qjXyj9TjI7pxanb/bWglhovJN9wIDAQABAoIBAQCSnMsLxbUfOxPx
|
|
|
|
RWuwOLN+NZxIvtfnastQEtSdWiRvo5Xa3zYmw5hLHa8DXRC57+cwug/jqr54LQpb
|
|
|
|
gotg1hiBck05In7ezTK2FXTVeoJskal91bUnLpP0DSOkVnz9xszFKNF6Wr7FTEfH
|
|
|
|
IC1FF16Fbcz0mW0hKg9X6+uYOzqPcKpQRwli5LAwhT18Alf9h4/3NCeKotiJyr2J
|
|
|
|
xvcEH1eY2m2c/jQZurBkys7qBC3+i8LJEOW8MBQt7mxajwfbU91wtP2YoqMcoYiS
|
|
|
|
zsPbYp7Ui2t4G9Yn+OJw+uj4RGP1Bo4nSyRxWDtg+8Zug/JYU6/s+8kVRpiGffd3
|
|
|
|
T1GvoxUhAoGBAOnPDWG/g1xlJf65Rh71CxMs638zhYbIloU2K4Rqr05DHe7GryTS
|
|
|
|
9hLVrwhHddK+KwfVbR8HFMPo1DC/NVbuKt8StTAadAu3HsC088gWd28nOiGAWuvH
|
|
|
|
Bo3x/DYQGYwGFfoo4rzCOgMj6DJjXmcWEXNv3NDMoXoYpkxa0g6zZDyHAoGBAMTL
|
|
|
|
t7EUneJT+Mm7wyL1I5bmaT/HFwqoUQB2ccBPVD8p1el62NgLdfhOa8iNlBVhMrlh
|
|
|
|
2aTjrMlSPcjr9sCgKrLcenSWw+2qFsf4+SmV01ntB9kWes2phXpnB0ynXIcbeG05
|
|
|
|
+BLxbqDTVV0Iqh4r/dGeplyV2WyL3mTpkT3hRq8RAoGAZ93degEUICWnHWO9LN97
|
|
|
|
Dge0joua0+ekRoVsC6VBP6k9UOfewqMdQfy/hxQH2Zk1kINVuKTyqp1yNj2bOoUP
|
|
|
|
co3jA/2cc9/jv4QjkE26vRxWDK/ytC90T/aiLno0fyns9XbYUzaNgvuemVPfijgZ
|
|
|
|
hIi7Nd7SFWWB6wWlr3YuH10CgYEAwh7JVa2mh8iZEjVaKTNyJbmmfDjgq6yYKkKr
|
|
|
|
ti0KRzv3O9Xn7ERx27tPaobtWaGFLYQt8g57NCMhuv23aw8Sz1fYmwTUw60Rx7P5
|
|
|
|
42FdF8lOAn/AJvpfJfxXIO+9v7ADPIr//3+TxqRwAdM4K4btWkaKh61wyTe26gfT
|
|
|
|
MxzyYmECgYAnlU5zsGyiZqwoXVktkhtZrE7Qu0SoztzFb8KpvFNmMTPF1kAAYmJY
|
|
|
|
GIhbizeGJ3h4cUdozKmt8ZWIt6uFDEYCqEA7XF4RH75dW25x86mpIPO7iRl9eisY
|
|
|
|
IsLeMYqTIwXAwGx6Ka9v5LOL1kzcHQ2iVj6+QX+yoptSft1dYa9jOA==
|
|
|
|
-----END RSA PRIVATE KEY-----`)
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
const rsaPrivKeyID = "ee69e8e07a14756ad5ff0aca2336b37f86b0ac1710d1f3e94440081e080aecd7"
|
|
|
|
|
|
|
|
var ecPrivKeyFixture = []byte(`-----BEGIN EC PRIVATE KEY-----
|
|
|
|
MHcCAQEEINfxKtDH3ug7ZIQPDyeAzujCdhw36D+bf9ToPE1A7YEyoAoGCCqGSM49
|
|
|
|
AwEHoUQDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4cH3nzy2O6Q/ct4BjOBKa+WCdR
|
|
|
|
tPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ==
|
|
|
|
-----END EC PRIVATE KEY-----`)
|
|
|
|
|
|
|
|
const ecPrivKeyID = "46157cb0becf9c72c3219e11d4692424fef9bf4460812ccc8a71a3dfcafc7e60"
|
|
|
|
|
|
|
|
var testKeys = map[string][]byte{
|
|
|
|
ecPrivKeyID: ecPrivKeyFixture,
|
|
|
|
rsaPrivKeyID: rsaPrivKeyFixture,
|
|
|
|
}
|
2017-09-26 20:16:18 -04:00
|
|
|
|
|
|
|
func TestLoadKeyFromPath(t *testing.T) {
|
2018-02-27 10:54:36 -05:00
|
|
|
skip.If(t, runtime.GOOS == "windows")
|
2017-10-10 13:16:01 -04:00
|
|
|
for keyID, keyBytes := range testKeys {
|
2023-11-20 07:04:09 -05:00
|
|
|
keyID, keyBytes := keyID, keyBytes
|
2017-10-10 13:16:01 -04:00
|
|
|
t.Run(fmt.Sprintf("load-key-id-%s-from-path", keyID), func(t *testing.T) {
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyFilepath := filepath.Join(t.TempDir(), "privkey.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, notary.PrivNoExecPerms))
|
2023-11-20 05:10:29 -05:00
|
|
|
|
|
|
|
keyStorageDir := t.TempDir()
|
|
|
|
|
|
|
|
const passwd = "password"
|
|
|
|
cannedPasswordRetriever := passphrase.ConstantRetriever(passwd)
|
|
|
|
keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
privKeyImporters := []trustmanager.Importer{keyFileStore}
|
|
|
|
|
|
|
|
// get the privKeyBytes
|
|
|
|
privKeyBytes, err := getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
// import the key to our keyStorageDir
|
|
|
|
assert.Check(t, loadPrivKeyBytesToStore(privKeyBytes, privKeyImporters, privKeyFilepath, "signer-name", cannedPasswordRetriever))
|
|
|
|
|
|
|
|
// check that the appropriate ~/<trust_dir>/private/<key_id>.key file exists
|
2023-11-20 07:04:09 -05:00
|
|
|
expectedImportKeyPath := filepath.Join(keyStorageDir, notary.PrivDir, keyID+"."+notary.KeyExtension)
|
2023-11-20 05:10:29 -05:00
|
|
|
_, err = os.Stat(expectedImportKeyPath)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
// verify the key content
|
|
|
|
from, _ := os.OpenFile(expectedImportKeyPath, os.O_RDONLY, notary.PrivExecPerms)
|
|
|
|
defer from.Close()
|
|
|
|
fromBytes, _ := io.ReadAll(from)
|
|
|
|
keyPEM, _ := pem.Decode(fromBytes)
|
|
|
|
assert.Check(t, is.Equal("signer-name", keyPEM.Headers["role"]))
|
|
|
|
// the default GUN is empty
|
|
|
|
assert.Check(t, is.Equal("", keyPEM.Headers["gun"]))
|
|
|
|
// assert encrypted header
|
|
|
|
assert.Check(t, is.Equal("ENCRYPTED PRIVATE KEY", keyPEM.Type))
|
|
|
|
|
|
|
|
decryptedKey, err := tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd))
|
|
|
|
assert.NilError(t, err)
|
2023-11-20 07:04:09 -05:00
|
|
|
fixturePEM, _ := pem.Decode(keyBytes)
|
2023-11-20 05:10:29 -05:00
|
|
|
assert.Check(t, is.DeepEqual(fixturePEM.Bytes, decryptedKey.Private()))
|
2017-10-10 13:16:01 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-26 20:16:18 -04:00
|
|
|
func TestLoadKeyTooPermissive(t *testing.T) {
|
2018-02-27 10:54:36 -05:00
|
|
|
skip.If(t, runtime.GOOS == "windows")
|
2017-10-10 13:16:01 -04:00
|
|
|
for keyID, keyBytes := range testKeys {
|
2023-11-20 07:04:09 -05:00
|
|
|
keyID, keyBytes := keyID, keyBytes
|
2017-10-10 13:16:01 -04:00
|
|
|
t.Run(fmt.Sprintf("load-key-id-%s-too-permissive", keyID), func(t *testing.T) {
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyDir := t.TempDir()
|
|
|
|
privKeyFilepath := filepath.Join(privKeyDir, "privkey477.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o477))
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
// import the key to our keyStorageDir
|
|
|
|
_, err := getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
expected := fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
|
|
|
|
assert.Error(t, err, expected)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyFilepath = filepath.Join(privKeyDir, "privkey667.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o677))
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
_, err = getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
|
|
|
|
assert.Error(t, err, expected)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyFilepath = filepath.Join(privKeyDir, "privkey777.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o777))
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
_, err = getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
expected = fmt.Sprintf("private key file %s must not be readable or writable by others", privKeyFilepath)
|
|
|
|
assert.Error(t, err, expected)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyFilepath = filepath.Join(privKeyDir, "privkey400.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o400))
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
_, err = getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
assert.NilError(t, err)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
privKeyFilepath = filepath.Join(privKeyDir, "privkey600.pem")
|
2023-11-20 07:04:09 -05:00
|
|
|
assert.NilError(t, os.WriteFile(privKeyFilepath, keyBytes, 0o600))
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2023-11-20 05:10:29 -05:00
|
|
|
_, err = getPrivKeyBytesFromPath(privKeyFilepath)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
})
|
|
|
|
}
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var pubKeyFixture = []byte(`-----BEGIN PUBLIC KEY-----
|
2017-10-25 13:45:10 -04:00
|
|
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4c
|
|
|
|
H3nzy2O6Q/ct4BjOBKa+WCdRtPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ==
|
|
|
|
-----END PUBLIC KEY-----`)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
|
|
|
func TestLoadPubKeyFailure(t *testing.T) {
|
2018-02-27 10:54:36 -05:00
|
|
|
skip.If(t, runtime.GOOS == "windows")
|
2022-02-25 08:33:57 -05:00
|
|
|
pubKeyDir := t.TempDir()
|
2017-09-26 20:16:18 -04:00
|
|
|
pubKeyFilepath := filepath.Join(pubKeyDir, "pubkey.pem")
|
2022-02-25 08:33:57 -05:00
|
|
|
assert.NilError(t, os.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms))
|
|
|
|
keyStorageDir := t.TempDir()
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2022-02-25 08:33:57 -05:00
|
|
|
const passwd = "password"
|
2017-09-26 20:16:18 -04:00
|
|
|
cannedPasswordRetriever := passphrase.ConstantRetriever(passwd)
|
|
|
|
keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension)
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2017-10-23 05:23:39 -04:00
|
|
|
privKeyImporters := []trustmanager.Importer{keyFileStore}
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
pubKeyBytes, err := getPrivKeyBytesFromPath(pubKeyFilepath)
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2017-10-10 13:16:01 -04:00
|
|
|
|
2017-09-26 20:16:18 -04:00
|
|
|
// import the key to our keyStorageDir - it should fail
|
2017-10-10 13:16:01 -04:00
|
|
|
err = loadPrivKeyBytesToStore(pubKeyBytes, privKeyImporters, pubKeyFilepath, "signer-name", cannedPasswordRetriever)
|
2017-12-21 16:27:57 -05:00
|
|
|
expected := fmt.Sprintf("provided file %s is not a supported private key - to add a signer's public key use docker trust signer add", pubKeyFilepath)
|
|
|
|
assert.Error(t, err, expected)
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|