diff --git a/cmd/duffle/build.go b/cmd/duffle/build.go index 39f53d01..c4a2280f 100644 --- a/cmd/duffle/build.go +++ b/cmd/duffle/build.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "fmt" "io" "io/ioutil" @@ -15,6 +14,7 @@ import ( dockerflags "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" "github.com/docker/go-connections/tlsconfig" + "github.com/docker/go/canonical/json" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -28,7 +28,6 @@ import ( "github.com/deislabs/duffle/pkg/imagebuilder/mock" "github.com/deislabs/duffle/pkg/ohai" "github.com/deislabs/duffle/pkg/repo" - "github.com/deislabs/duffle/pkg/signature" ) const buildDesc = ` @@ -50,7 +49,6 @@ type buildCmd struct { out io.Writer src string home home.Home - signer string outputFile string // options common to the docker client and the daemon. @@ -87,7 +85,6 @@ func newBuildCmd(out io.Writer) *cobra.Command { } f = cmd.Flags() - f.StringVarP(&build.signer, "user", "u", "", "the user ID of the signing key to use. Format is either email address or 'NAME (COMMENT) '") f.StringVarP(&build.outputFile, "output-file", "o", "", "If set, writes the bundle to this file in addition to saving it to the local store") f.BoolVar(&build.dockerClientOptions.Common.Debug, "docker-debug", false, "Enable debug mode") @@ -152,30 +149,11 @@ func (b *buildCmd) run() (err error) { } func (b *buildCmd) writeBundle(bf *bundle.Bundle) (string, error) { - kr, err := signature.LoadKeyRing(b.home.SecretKeyRing()) + data, err := json.MarshalCanonical(bf) if err != nil { - return "", fmt.Errorf("cannot load keyring: %s", err) - } - - if kr.Len() == 0 { - return "", errors.New("no signing keys are present in the keyring") - } - - // Default to the first key in the ring unless the user specifies otherwise. - key := kr.Keys()[0] - if b.signer != "" { - key, err = kr.Key(b.signer) - if err != nil { - return "", err - } - } - - sign := signature.NewSigner(key) - data, err := sign.Clearsign(bf) - data = append(data, '\n') - if err != nil { - return "", fmt.Errorf("cannot sign bundle: %s", err) + return "", err } + data = append(data, '\n') //TODO: why? digest, err := digest.OfBuffer(data) if err != nil { diff --git a/cmd/duffle/build_test.go b/cmd/duffle/build_test.go index a07c17b7..886d5973 100644 --- a/cmd/duffle/build_test.go +++ b/cmd/duffle/build_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "errors" "io" "io/ioutil" "os" @@ -11,9 +10,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/deislabs/duffle/pkg/duffle/home" "github.com/deislabs/duffle/pkg/repo" - "github.com/deislabs/duffle/pkg/signature" ) func TestBuild(t *testing.T) { @@ -54,14 +51,11 @@ func TestBuild(t *testing.T) { out: out, } - // Create temporary signing key - mockSigningKeyring(testHome.String(), t) - if err := cmd.run(); err != nil { t.Errorf("Expected no error but got err: %s", err) } - // Verify that the bundle exists and is signed + // Verify that the bundle exists is := assert.New(t) index, err := repo.LoadIndex(testHome.Repositories()) @@ -77,22 +71,6 @@ func TestBuild(t *testing.T) { loc := filepath.Join(testHome.Bundles(), digest) is.FileExists(loc) - data, err := ioutil.ReadFile(loc) + _, err = ioutil.ReadFile(loc) is.NoError(err) - is.Contains(string(data), "---BEGIN PGP SIGNED MESSAGE----") -} - -func mockSigningKeyring(tempHome string, t *testing.T) { - t.Helper() - uid, err := signature.ParseUserID("fake ") - if err != nil { - t.Fatal(err) - } - ring := signature.CreateKeyRing(func(a string) ([]byte, error) { return nil, errors.New("not implemented") }) - key, err := signature.CreateKey(uid) - if err != nil { - t.Fatal(err) - } - ring.AddKey(key) - ring.SavePrivate(home.Home(tempHome).SecretKeyRing(), true) } diff --git a/cmd/duffle/bundle.go b/cmd/duffle/bundle.go index 4ee53b5a..82dcd649 100644 --- a/cmd/duffle/bundle.go +++ b/cmd/duffle/bundle.go @@ -21,8 +21,6 @@ func newBundleCmd(w io.Writer) *cobra.Command { newBundleListCmd(w), newInstallCmd(w), newBundleShowCmd(w), - newBundleSignCmd(w), - newBundleVerifyCmd(w), newBundleRemoveCmd(w), ) return cmd diff --git a/cmd/duffle/bundle_list.go b/cmd/duffle/bundle_list.go index 331853cf..44cde29f 100644 --- a/cmd/duffle/bundle_list.go +++ b/cmd/duffle/bundle_list.go @@ -34,7 +34,6 @@ type NamedRepository struct { name string tag string digest string - signed bool } // Name returns the full name. @@ -57,11 +56,6 @@ func (n *NamedRepository) Digest() string { return n.digest } -// IsSigned determines whether or not the bundle is signed. -func (n *NamedRepository) IsSigned() bool { - return n.signed -} - func newBundleListCmd(w io.Writer) *cobra.Command { var short bool cmd := &cobra.Command{ @@ -83,9 +77,9 @@ func newBundleListCmd(w io.Writer) *cobra.Command { } table := uitable.New() - table.AddRow("NAME", "VERSION", "DIGEST", "SIGNED?") + table.AddRow("NAME", "VERSION", "DIGEST") for _, ref := range references { - table.AddRow(ref.Name(), ref.Tag(), ref.Digest(), ref.IsSigned()) + table.AddRow(ref.Name(), ref.Tag(), ref.Digest()) } fmt.Fprintln(w, table) @@ -107,16 +101,14 @@ func searchLocal(home home.Home) (NamedRepositoryList, error) { for repo, tagList := range index { for tag, digest := range tagList { - isSigned := true - _, err := loadBundle(filepath.Join(home.Bundles(), digest), true) - if err == ErrNotSigned { - isSigned = false + _, err := loadBundle(filepath.Join(home.Bundles(), digest)) + if err != nil { + return nil, err } references = append(references, &NamedRepository{ repo, tag, digest, - isSigned, }) } } diff --git a/cmd/duffle/bundle_remove_test.go b/cmd/duffle/bundle_remove_test.go index 31ae9d03..610437f0 100644 --- a/cmd/duffle/bundle_remove_test.go +++ b/cmd/duffle/bundle_remove_test.go @@ -19,7 +19,7 @@ func TestBundleRemove(t *testing.T) { if err := os.MkdirAll(duffleHome.Bundles(), 0755); err != nil { t.Fatal(err) } - if err := copySignedTestBundle(tempDuffleHome); err != nil { + if err := copyTestBundle(tempDuffleHome); err != nil { t.Fatal(err) } @@ -33,7 +33,7 @@ func TestBundleRemove(t *testing.T) { t.Errorf("Did not expect error, got %s", err) } - if _, err := os.Stat(filepath.Join(cmd.home.Bundles(), "foo-1.0.0.cnab")); !os.IsNotExist(err) { + if _, err := os.Stat(filepath.Join(cmd.home.Bundles(), "foo-1.0.0.json")); !os.IsNotExist(err) { t.Errorf("Expected bundle file to be removed from local store but was not") } } diff --git a/cmd/duffle/bundle_show.go b/cmd/duffle/bundle_show.go index e80c60db..88ccf734 100644 --- a/cmd/duffle/bundle_show.go +++ b/cmd/duffle/bundle_show.go @@ -11,10 +11,9 @@ import ( const bundleShowShortUsage = `return low-level information on application bundles` type bundleShowCmd struct { - name string - insecure bool - raw bool - w io.Writer + name string + raw bool + w io.Writer } func newBundleShowCmd(w io.Writer) *cobra.Command { @@ -34,7 +33,6 @@ func newBundleShowCmd(w io.Writer) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&bsc.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") flags.BoolVarP(&bsc.raw, "raw", "r", false, "Display the raw bundle manifest") return cmd @@ -51,13 +49,11 @@ func (bsc *bundleShowCmd) usage(bundleSubCommand bool) string { Example: $ duffle %s duffle/example:0.1.0 - To display unsigned bundles, pass the --insecure flag: - $ duffle %s duffle/unsinged-example:0.1.0 --insecure -`, commandName, commandName) +`, commandName) } func (bsc *bundleShowCmd) run() error { - bundleFile, err := getBundleFilepath(bsc.name, homePath(), bsc.insecure) + bundleFile, err := getBundleFilepath(bsc.name, homePath()) if err != nil { return err } @@ -72,7 +68,7 @@ func (bsc *bundleShowCmd) run() error { return err } - bun, err := loadBundle(bundleFile, bsc.insecure) + bun, err := loadBundle(bundleFile) if err != nil { return err } diff --git a/cmd/duffle/bundle_sign.go b/cmd/duffle/bundle_sign.go deleted file mode 100644 index d0f69ebb..00000000 --- a/cmd/duffle/bundle_sign.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/deislabs/duffle/pkg/bundle" - "github.com/deislabs/duffle/pkg/crypto/digest" - "github.com/deislabs/duffle/pkg/duffle/home" - "github.com/deislabs/duffle/pkg/signature" -) - -const bundleSignDesc = `Clear-sign a bundle. - -This remarshals the bundle.json into canonical form, and then clear-signs the JSON. -By default, the signed bundle is written to $DUFFLE_HOME. You can specify an output-file to save to instead using the flag. - -If no key name is supplied, this uses the first signing key in the secret keyring. -` - -type bundleSignCmd struct { - out io.Writer - home home.Home - identity string - bundleFile string - outfile string - skipValidation bool -} - -func newBundleSignCmd(w io.Writer) *cobra.Command { - sign := &bundleSignCmd{out: w} - - cmd := &cobra.Command{ - Use: "sign BUNDLE", - Short: "clear-sign a bundle", - Args: cobra.MaximumNArgs(1), - Long: bundleSignDesc, - RunE: func(cmd *cobra.Command, args []string) error { - sign.home = home.Home(homePath()) - secring := sign.home.SecretKeyRing() - bundle, err := bundleFileOrArg1(args, sign.bundleFile) - if err != nil { - return err - } - return sign.signBundle(bundle, secring) - }, - } - cmd.Flags().StringVarP(&sign.identity, "user", "u", "", "the user ID of the key to use. Format is either email address or 'NAME (COMMENT) '") - cmd.Flags().StringVarP(&sign.bundleFile, "file", "f", "", "path to bundle file to sign") - cmd.Flags().StringVarP(&sign.outfile, "output-file", "o", "", "the name of the output file") - cmd.Flags().BoolVar(&sign.skipValidation, "skip-validate", false, "do not validate the JSON before marshaling it.") - - return cmd -} - -func bundleFileOrArg1(args []string, bundle string) (string, error) { - switch { - case len(args) == 1 && bundle != "": - return "", errors.New("please use either -f or specify a BUNDLE, but not both") - case len(args) == 0 && bundle == "": - return "", errors.New("please specify a BUNDLE or use -f for a file") - case len(args) == 1: - // passing insecure: true, as currently we can only sign an unsinged bundle - return getBundleFilepath(args[0], homePath(), true) - } - return bundle, nil -} - -func (bs *bundleSignCmd) signBundle(bundleFile, keyring string) error { - // Verify that file exists - if fi, err := os.Stat(bundleFile); err != nil { - return fmt.Errorf("cannot find bundle file to sign: %v", err) - } else if fi.IsDir() { - return errors.New("cannot sign a directory") - } - - bdata, err := ioutil.ReadFile(bundleFile) - if err != nil { - return err - } - b, err := bundle.Unmarshal(bdata) - if err != nil { - return err - } - - if !bs.skipValidation { - if err := b.Validate(); err != nil { - return err - } - } - - // Load keyring - kr, err := signature.LoadKeyRing(keyring) - if err != nil { - return err - } - // Find identity - var k *signature.Key - if bs.identity != "" { - k, err = kr.Key(bs.identity) - if err != nil { - return err - } - } else { - all := kr.PrivateKeys() - if len(all) == 0 { - return errors.New("no private keys found") - } - k = all[0] - } - - // Be sure userID is parseable before attempting to sign - userID, err := k.UserID() - if err != nil { - return err - } - - // Sign the file - s := signature.NewSigner(k) - data, err := s.Clearsign(b) - if err != nil { - return err - } - - data = append(data, '\n') - - digest, err := digest.OfBuffer(data) - if err != nil { - return fmt.Errorf("cannot compute digest from bundle: %v", err) - } - - // if --output-file is provided, write and return - if bs.outfile != "" { - if err := ioutil.WriteFile(bs.outfile, data, 0644); err != nil { - return err - } - } - - if err := ioutil.WriteFile(filepath.Join(bs.home.Bundles(), digest), data, 0644); err != nil { - return err - } - - // TODO - write pkg method in bundle that writes file and records the reference - if err := recordBundleReference(bs.home, b.Name, b.Version, digest); err != nil { - return err - } - - fmt.Fprintf(bs.out, "Signed by %s %s \n", userID.String(), k.Fingerprint()) - return nil -} diff --git a/cmd/duffle/bundle_sign_test.go b/cmd/duffle/bundle_sign_test.go deleted file mode 100644 index 4e790136..00000000 --- a/cmd/duffle/bundle_sign_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/deislabs/duffle/pkg/duffle/home" -) - -func TestBundleSign(t *testing.T) { - tempDir, err := ioutil.TempDir("", "duffle-home") - if err != nil { - t.Fatal(err) - } - if err := os.Mkdir(filepath.Join(tempDir, "bundles"), 0755); err != nil { - t.Fatal(err) - } - - tmp, err := ioutil.TempFile("", "duffle-") - if err != nil { - t.Fatal(err) - } - outfile := tmp.Name() - defer os.Remove(outfile) - - bundlejson := filepath.Join("..", "..", "tests", "testdata", "bundles", "foo.json") - keyring := filepath.Join("..", "..", "pkg", "signature", "testdata", "keyring.gpg") - identity := "test2@example.com" - - cmd := bundleSignCmd{ - outfile: outfile, - identity: identity, - out: ioutil.Discard, - home: home.Home(tempDir), - } - if err := cmd.signBundle(bundlejson, keyring); err != nil { - t.Fatal(err) - } - - data, err := ioutil.ReadFile(outfile) - if err != nil { - t.Fatal(err) - } - - sig := string(data) - is := assert.New(t) - is.Contains(sig, "-----BEGIN PGP SIGNATURE-----") -} diff --git a/cmd/duffle/bundle_verify.go b/cmd/duffle/bundle_verify.go deleted file mode 100644 index 6cc085dc..00000000 --- a/cmd/duffle/bundle_verify.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - - "github.com/deislabs/duffle/pkg/duffle/home" - "github.com/deislabs/duffle/pkg/signature" - - "github.com/spf13/cobra" -) - -const bundleVerifyDesc = `Verify a signed bundle. - -This command verifies the signature by checking it against both the public -and secret keyrings. A bundle is verified if and only if a key exists in the -keyring(s) that can successfully decrypt the signature and verify the hash. -` - -func newBundleVerifyCmd(w io.Writer) *cobra.Command { - var ( - public bool - bundleFile string - ) - - cmd := &cobra.Command{ - Use: "verify BUNDLE", - Short: "verify the signature on a signed bundle", - Long: bundleVerifyDesc, - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - h := home.Home(homePath()) - secret := h.SecretKeyRing() - public := h.PublicKeyRing() - - bundle, err := bundleFileOrArg1(args, bundleFile) - if err != nil { - return err - } - - return verifySig(bundle, public, secret, w) - }, - } - cmd.Flags().BoolVarP(&public, "public", "p", false, "show public key IDs instead of private key IDs") - cmd.Flags().StringVarP(&bundleFile, "file", "f", "", "path to bundle file to sign") - - return cmd -} - -func verifySig(filename, public, private string, out io.Writer) error { - data, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - - ring, err := signature.LoadKeyRing(public) - if err != nil { - return err - } - - // We want the private keyring because a user should be able to verify - // any bundles that they signed, and their signing key is in the - // private keyring. - priv, err := signature.LoadKeyRing(private) - if err != nil { - return err - } - for _, pk := range priv.Keys() { - ring.AddKey(pk) - } - - verifier := signature.NewVerifier(ring) - key, err := verifier.Verify(data) - if err != nil { - return fmt.Errorf("verification failed: %s ", err) - } - - user, err := key.UserID() - signed := "[anonymous key]" - if err == nil { - signed = user.String() - } - - fmt.Fprintf(out, "Signed by %q (%s)\n", signed, key.Fingerprint()) - return nil -} diff --git a/cmd/duffle/credential_generate.go b/cmd/duffle/credential_generate.go index 52452815..31b8e09f 100644 --- a/cmd/duffle/credential_generate.go +++ b/cmd/duffle/credential_generate.go @@ -53,7 +53,7 @@ func newCredentialGenerateCmd(out io.Writer) *cobra.Command { } csName := args[0] - bun, err := loadBundle(bf, insecure) + bun, err := loadBundle(bf) if err != nil { return err } @@ -187,7 +187,7 @@ func getBundleFileFromCredentialsArg(args []string, bundleFile string, w io.Writ case len(args) < 2 && bundleFile == "": return "", errors.New("required arguments are NAME (name for the credentialset) and BUNDLE (CNAB bundle name) or file") case len(args) == 2: - return getBundleFilepath(args[1], homePath(), insecure) + return getBundleFilepath(args[1], homePath()) } return bundleFile, nil } diff --git a/cmd/duffle/default_user.go b/cmd/duffle/default_user.go deleted file mode 100644 index 858b16fc..00000000 --- a/cmd/duffle/default_user.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/user" - "strings" - - "github.com/deislabs/duffle/pkg/signature" -) - -// defaultUserID returns the default user name. -func defaultUserID() signature.UserID { - // TODO: I am not sure how reliable this is on Windows. - domain, err := os.Hostname() - if err != nil { - domain = "localhost.localdomain" - } - var name, username string - - if account, err := user.Current(); err != nil { - name = "user" - username = name - } else { - name = account.Name - username = account.Username - } - - // on Windows, account names are prefixed with '\' which makes the generated email invalid - // and makes the key user identity parser fail - if ix := strings.Index(username, "\\"); ix != -1 { - username = username[ix+1:] - } - - email := fmt.Sprintf("%s@%s", username, domain) - return signature.UserID{ - Name: name, - Email: email, - } -} diff --git a/cmd/duffle/export.go b/cmd/duffle/export.go index d4032cf3..93157649 100644 --- a/cmd/duffle/export.go +++ b/cmd/duffle/export.go @@ -38,7 +38,6 @@ type exportCmd struct { out io.Writer thin bool verbose bool - insecure bool bundleIsFile bool } @@ -63,7 +62,6 @@ func newExportCmd(w io.Writer) *cobra.Command { f.BoolVarP(&export.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the bundle source is a file path") f.BoolVarP(&export.thin, "thin", "t", false, "Export only the bundle manifest") f.BoolVarP(&export.verbose, "verbose", "v", false, "Verbose output") - f.BoolVarP(&export.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") return cmd } @@ -81,7 +79,7 @@ func (ex *exportCmd) run() error { } func (ex *exportCmd) Export(bundlefile string, l loader.Loader) error { - exp, err := packager.NewExporter(bundlefile, ex.dest, ex.home.Logs(), l, ex.thin, ex.insecure) + exp, err := packager.NewExporter(bundlefile, ex.dest, ex.home.Logs(), l, ex.thin) if err != nil { return fmt.Errorf("Unable to set up exporter: %s", err) } @@ -95,26 +93,21 @@ func (ex *exportCmd) Export(bundlefile string, l loader.Loader) error { } func (ex *exportCmd) setup() (string, loader.Loader, error) { - bundlefile, err := resolveBundleFilePath(ex.bundle, ex.home.String(), ex.bundleIsFile, ex.insecure) + bundlefile, err := resolveBundleFilePath(ex.bundle, ex.home.String(), ex.bundleIsFile) if err != nil { return "", nil, err } - l, err := getLoader(ex.home.String(), ex.insecure) - if err != nil { - return "", nil, err - } - - return bundlefile, l, nil + return bundlefile, loader.NewDetectingLoader(), nil } -func resolveBundleFilePath(bun, homePath string, bundleIsFile, insecure bool) (string, error) { +func resolveBundleFilePath(bun, homePath string, bundleIsFile bool) (string, error) { if bundleIsFile { return bun, nil } - bundlefile, err := getBundleFilepath(bun, homePath, insecure) + bundlefile, err := getBundleFilepath(bun, homePath) if err != nil { return "", err } diff --git a/cmd/duffle/export_test.go b/cmd/duffle/export_test.go index b1aafc2f..d28f87b5 100644 --- a/cmd/duffle/export_test.go +++ b/cmd/duffle/export_test.go @@ -22,13 +22,6 @@ func setupTempDuffleHome(t *testing.T) (string, error) { if err := os.MkdirAll(duffleHome.Logs(), 0755); err != nil { return "", err } - keyring := filepath.Join("..", "..", "pkg", "signature", "testdata", "keyring.gpg") - - err = copyFile(keyring, filepath.Join(tempDuffleHome, "public.ring")) - if err != nil { - return "", err - } - mockSigningKeyring(tempDuffleHome, t) return tempDuffleHome, nil } @@ -41,7 +34,7 @@ func TestExportSetup(t *testing.T) { } defer os.Remove(tempDuffleHome) - if err := copySignedTestBundle(tempDuffleHome); err != nil { + if err := copyTestBundle(tempDuffleHome); err != nil { t.Fatal(err) } @@ -64,7 +57,7 @@ func TestExportSetup(t *testing.T) { t.Errorf("Did not expect error but got %s", err) } - expectedSource := filepath.Join(tempDuffleHome, "bundles", "foo-1.0.0.cnab") + expectedSource := filepath.Join(tempDuffleHome, "bundles", "foo-1.0.0.json") if source != expectedSource { t.Errorf("Expected source to be %s, got %s", expectedSource, source) } @@ -117,15 +110,15 @@ func copyFile(src, dst string) (err error) { return nil } -func copySignedTestBundle(tempDuffleHome string) error { - signedBundle := filepath.Join("..", "..", "tests", "testdata", "bundles", "foo.cnab") - outfile := "foo-1.0.0.cnab" - if err := copyFile(signedBundle, filepath.Join(tempDuffleHome, "bundles", outfile)); err != nil { +func copyTestBundle(tempDuffleHome string) error { + bun := filepath.Join("..", "..", "tests", "testdata", "bundles", "foo.json") + outfile := "foo-1.0.0.json" + if err := copyFile(bun, filepath.Join(tempDuffleHome, "bundles", outfile)); err != nil { return err } var jsonBlob = []byte(`{ "foo": { - "1.0.0": "foo-1.0.0.cnab" + "1.0.0": "foo-1.0.0.json" } } `) return ioutil.WriteFile(filepath.Join(tempDuffleHome, "repositories.json"), jsonBlob, 0644) diff --git a/cmd/duffle/import.go b/cmd/duffle/import.go index 3eaf837f..51620dce 100644 --- a/cmd/duffle/import.go +++ b/cmd/duffle/import.go @@ -5,10 +5,11 @@ import ( "io" "path/filepath" + "github.com/spf13/cobra" + "github.com/deislabs/duffle/pkg/duffle/home" + "github.com/deislabs/duffle/pkg/loader" "github.com/deislabs/duffle/pkg/packager" - - "github.com/spf13/cobra" ) const importDesc = ` @@ -16,12 +17,11 @@ Unpacks a bundle from a gzipped tar file on local file system ` type importCmd struct { - source string - dest string - out io.Writer - home home.Home - insecure bool - verbose bool + source string + dest string + out io.Writer + home home.Home + verbose bool } func newImportCmd(w io.Writer) *cobra.Command { @@ -46,7 +46,6 @@ func newImportCmd(w io.Writer) *cobra.Command { f := cmd.Flags() f.StringVarP(&importc.dest, "destination", "d", "", "Location to unpack bundle") - f.BoolVarP(&importc.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") f.BoolVarP(&importc.verbose, "verbose", "v", false, "Verbose output") return cmd @@ -63,11 +62,7 @@ func (im *importCmd) run() error { return err } - l, err := getLoader(im.home.String(), im.insecure) - if err != nil { - return err - } - + l := loader.NewDetectingLoader() imp, err := packager.NewImporter(source, dest, l, im.verbose) if err != nil { return err diff --git a/cmd/duffle/init.go b/cmd/duffle/init.go index f12261b6..4b6ad4f1 100644 --- a/cmd/duffle/init.go +++ b/cmd/duffle/init.go @@ -1,18 +1,15 @@ package main import ( - "errors" "fmt" "io" "os" "strings" "github.com/spf13/cobra" - survey "gopkg.in/AlecAivazis/survey.v1" "github.com/deislabs/duffle/pkg/duffle/home" "github.com/deislabs/duffle/pkg/ohai" - "github.com/deislabs/duffle/pkg/signature" ) const ( @@ -20,28 +17,17 @@ const ( Explicitly control the creation of the Duffle environment. Normally, Duffle initializes itself. But on occasion, you may wish to customize Duffle's initialization, -passing your own keys or testing to see what directories will be created. This command is provided for -such a reason. + or testing to see what directories will be created. This command is provided for such a reason. This command will create a subdirectory in your home directory, and use that directory for storing -configuration, preferences, and persistent data. Duffle uses OpenPGP-style keys for signing and -verification. If you do not provide a secret key to import, the init phase will generate a keyring for -you, and create a signing key. - -During initialization, you may use '--public-keys' to import a keyring of public keys. These keys will -then be used by other commands (such as 'duffle install') to verify the integrity of a package. If -you do not supply keys during initialization, you will need to provide them later. WARNING: You should -not import private keys with the '--public-keys' flag, or they may be placed in your public keyring. +configuration, preferences, and persistent data. ` ) type initCmd struct { - dryRun bool - keyFile string - username string - w io.Writer - pubkeyFile string - verbose bool + dryRun bool + w io.Writer + verbose bool } func newInitCmd(w io.Writer) *cobra.Command { @@ -53,18 +39,12 @@ func newInitCmd(w io.Writer) *cobra.Command { Long: initDesc, Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - if i.keyFile != "" && i.username != "" { - fmt.Fprintln(os.Stderr, "WARNING: 'user' and 'signing-key' were both provided. Ignoring 'user'.") - } return i.run() }, } f := cmd.Flags() f.BoolVar(&i.dryRun, "dry-run", false, "go through all the steps without actually installing anything") - f.StringVarP(&i.keyFile, "signing-key", "k", "", "Armored OpenPGP key to be used for signing. If not specified, one will be generated for you") - f.StringVarP(&i.pubkeyFile, "public-keys", "p", "", "Armored OpenPGP key containing trusted public keys. If not specified, no public keys will be trusted by default") - f.StringVarP(&i.username, "user", "u", "", "User identity for the OpenPGP key. The format is 'NAME (OPTIONAL COMMENT) '.") return cmd } @@ -115,12 +95,7 @@ func (i *initCmd) run() error { } } - pkr, err := i.loadOrCreateSecretKeyRing(home.SecretKeyRing()) - if err != nil { - return err - } - _, err = i.loadOrCreatePublicKeyRing(home.PublicKeyRing(), pkr) - return err + return nil } func ensureDirectories(dirs []string) error { @@ -146,131 +121,3 @@ func ensureFiles(files []string) error { } return nil } - -// This loads a keyring from disk. If no keyring already exists, this will create a new -// keyring, add a new default identity, and then write that keyring to disk. -// -// Regardless of the path, a *signature.KeyRing will be returned. -func (i *initCmd) loadOrCreateSecretKeyRing(dest string) (*signature.KeyRing, error) { - if _, err := os.Stat(dest); err == nil { - // Since this is non-mutating, we can do this in a dry-run. - return signature.LoadKeyRing(dest) - } else if !os.IsNotExist(err) { - return nil, err - } - - fmt.Fprintf(i.w, "==> Generating a new secret keyring at %s\n", dest) - - // We could probably move the dry-run to just before the `ring.Save`. Not sure - // what that accomplishes, though. - if i.dryRun { - return &signature.KeyRing{}, nil - } - - ring := signature.CreateKeyRing(passwordFetcher) - if i.keyFile != "" { - key, err := os.Open(i.keyFile) - if err != nil { - return ring, err - } - err = ring.Add(key, true) - key.Close() - if err != nil { - return ring, err - } - - if all := ring.PrivateKeys(); len(all) == 0 { - // If we have no private keys, this is probably an error condition, since - // signing will be broken. - return ring, errors.New("no private keys were found in the key file") - } - - for _, k := range ring.PrivateKeys() { - i.printUserID(k) - } - } else { - var user signature.UserID - if i.username != "" { - var err error - user, err = signature.ParseUserID(i.username) - if err != nil { - return ring, err - } - } else { - user = defaultUserID() - } - // Generate the key - fmt.Fprintf(i.w, "==> Generating a new signing key with ID %s\n", user.String()) - k, err := signature.CreateKey(user) - if err != nil { - return ring, err - } - ring.AddKey(k) - } - err := ring.SavePrivate(dest, false) - if err != nil { - return ring, err - } - - return ring, os.Chmod(dest, 0600) - -} - -// loadOrCreatePublicKeyRing creates a ring of public keys. -// If the privateKeys are passed in, the public keys for each is then saved in the public keyring. -// This is useful if you need to verify things that were signed via one of the private keys on -// the secret ring. -func (i *initCmd) loadOrCreatePublicKeyRing(dest string, privateKeys *signature.KeyRing) (*signature.KeyRing, error) { - if _, err := os.Stat(dest); err == nil { - // Since this is non-mutating, we can do this in a dry-run. - return signature.LoadKeyRing(dest) - } else if !os.IsNotExist(err) { - return nil, err - } - - fmt.Fprintf(i.w, "==> Generating a new public keyring at %s\n", dest) - - // We could probably move the dry-run to just before the `ring.Save`. Not sure - // what that accomplishes, though. - if i.dryRun { - return &signature.KeyRing{}, nil - } - ring := signature.CreateKeyRing(passwordFetcher) - - if i.pubkeyFile != "" { - keys, err := os.Open(i.pubkeyFile) - if err != nil { - return ring, err - } - err = ring.Add(keys, true) - keys.Close() - if err != nil { - return ring, err - } - } - - for _, k := range ring.Keys() { - i.printUserID(k) - } - - return ring, ring.SavePublic(dest, false, false) -} - -func (i *initCmd) printUserID(k *signature.Key) { - uid, err := k.UserID() - if err != nil { - fmt.Fprintln(i.w, "==> Importing anonymous key") - return - } - fmt.Fprintf(i.w, "==> Importing %q\n", uid) -} - -// passwordFetcher is a simple prompt-based no-echo password input. -func passwordFetcher(prompt string) ([]byte, error) { - var pw string - err := survey.AskOne(&survey.Password{ - Message: fmt.Sprintf("Passphrase for key %q > ", prompt), - Help: "Unlock a passphrase-protected key", - }, &pw, nil) - return []byte(pw), err -} diff --git a/cmd/duffle/install.go b/cmd/duffle/install.go index baa3cfef..035e823d 100644 --- a/cmd/duffle/install.go +++ b/cmd/duffle/install.go @@ -49,21 +49,6 @@ Windows Example: You can also load the bundle.json file directly: $ duffle install dev_bundle path/to/bundle.json --bundle-is-file - -Verifying and --insecure: -When the --insecure flag is passed, verification steps will not be -performed. This means that Duffle will accept both unsigned -(bundle.json) and signed (bundle.cnab) files, but will not perform -any validation. The following table illustrates this: - - Bundle Key known? Flag Result - ------ ---------- ----------- ------ - Signed Known None Okay - Signed Known --insecure Okay - Signed Unknown None Verification error - Signed Unknown --insecure Okay - Unsigned N/A None Verification error - Unsigned N/A --insecure Okay ` type installCmd struct { @@ -75,7 +60,6 @@ type installCmd struct { credentialsFiles []string valuesFile string setParams []string - insecure bool setFiles []string bundleIsFile bool name string @@ -99,7 +83,6 @@ func newInstallCmd(w io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVarP(&install.bundleIsFile, "bundle-is-file", "f", false, "Indicates that the bundle source is a file path") - f.BoolVarP(&install.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") f.StringVarP(&install.driver, "driver", "d", "docker", "Specify a driver name") f.StringVarP(&install.valuesFile, "parameters", "p", "", "Specify file containing parameters. Formats: toml, MORE SOON") f.StringArrayVarP(&install.credentialsFiles, "credentials", "c", []string{}, "Specify credentials to use inside the bundle. This can be a credentialset name or a path to a file.") @@ -110,7 +93,7 @@ func newInstallCmd(w io.Writer) *cobra.Command { } func (i *installCmd) run() error { - bundleFile, err := resolveBundleFilePath(i.bundle, i.home.String(), i.bundleIsFile, i.insecure) + bundleFile, err := resolveBundleFilePath(i.bundle, i.home.String(), i.bundleIsFile) if err != nil { return err } @@ -120,7 +103,7 @@ func (i *installCmd) run() error { return fmt.Errorf("a claim with the name %v already exists", i.name) } - bun, err := loadBundle(bundleFile, i.insecure) + bun, err := loadBundle(bundleFile) if err != nil { return err } @@ -168,7 +151,7 @@ func (i *installCmd) run() error { return err2 } -func getBundleFilepath(bun, homePath string, insecure bool) (string, error) { +func getBundleFilepath(bun, homePath string) (string, error) { home := home.Home(homePath) ref, err := getReference(bun) if err != nil { diff --git a/cmd/duffle/install_test.go b/cmd/duffle/install_test.go index bf3a149f..3fa832f6 100644 --- a/cmd/duffle/install_test.go +++ b/cmd/duffle/install_test.go @@ -41,7 +41,7 @@ func TestGetBundle(t *testing.T) { tc := tc // capture range variable t.Run(tc.Name, func(t *testing.T) { t.Parallel() - bundle, err := getBundleFilepath(tc.File, duffleHome, true) + bundle, err := getBundleFilepath(tc.File, duffleHome) if err != nil { t.Error(err) } diff --git a/cmd/duffle/key.go b/cmd/duffle/key.go deleted file mode 100644 index 2fbdb19c..00000000 --- a/cmd/duffle/key.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "io" - - "github.com/spf13/cobra" -) - -const keyDesc = ` -Manages OpenPGP keys, signatures, and attestations. -` - -// TODO -func newKeyCmd(w io.Writer) *cobra.Command { - cmd := &cobra.Command{ - Use: "key", - Aliases: []string{"keys"}, - Short: "manage keys", - Long: keyDesc, - } - cmd.AddCommand( - newKeyImportCmd(w), - newKeyListCmd(w), - newKeyExportCmd(w), - ) - return cmd -} diff --git a/cmd/duffle/key_export.go b/cmd/duffle/key_export.go deleted file mode 100644 index bd27bba6..00000000 --- a/cmd/duffle/key_export.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io" - "os" - - "github.com/deislabs/duffle/pkg/duffle/home" - "github.com/deislabs/duffle/pkg/signature" - - "github.com/spf13/cobra" -) - -const keyExportDesc = `Export the public key part of a signing key. - -This exports a public key to a file (as a keyring). If the output file already -exists, the new key will be added. Importantly, if other private keys exist in -the file, the private key material will be removed from those as well. - -If no key name is given, the default signing key is exported. -` - -func newKeyExportCmd(w io.Writer) *cobra.Command { - var ( - dest string - keyname string - armored bool - ) - cmd := &cobra.Command{ - Use: "export FILE", - Short: "export the public key of a signing key", - Long: keyExportDesc, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - h := home.Home(homePath()) - dest = args[0] - - ring, err := signature.LoadKeyRing(h.SecretKeyRing()) - if err != nil { - return err - } - - // Get either the named key or the first key in the ring - var key *signature.Key - if keyname == "" { - allKeys := ring.Keys() - if len(allKeys) == 0 { - return errors.New("no keys found in signing ring") - } - key = allKeys[0] - } else { - key, err = ring.Key(keyname) - if err != nil { - return err - } - } - - uid, err := key.UserID() - if err != nil { - return fmt.Errorf("could not load key with ID %q: %s", keyname, err) - } - if verbose { - fmt.Fprintf(w, "found key %q matching %q\n", uid.String(), keyname) - } - - // Send to the destination, or to STDOUT - if fi, err := os.Stat(dest); os.IsNotExist(err) { - kr := signature.CreateKeyRing(passwordFetcher) - kr.AddKey(key) - return kr.SavePublic(dest, true, armored) - } else if err != nil { - return err - } else if fi.IsDir() { - return errors.New("destination cannot be a directory") - } - - kr, err := signature.LoadKeyRing(dest) - if err != nil { - return err - } - kr.AddKey(key) - - for _, k := range kr.Keys() { - uid, _ := k.UserID() - println(uid.String()) - } - return kr.SavePublic(dest, true, armored) - - }, - } - flags := cmd.Flags() - flags.StringVarP(&keyname, "user", "u", "", "The user ID of the key to export") - flags.BoolVarP(&armored, "armored", "a", false, "Export an ASCII armored key") - return cmd -} diff --git a/cmd/duffle/key_import.go b/cmd/duffle/key_import.go deleted file mode 100644 index b0fa4407..00000000 --- a/cmd/duffle/key_import.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "io" - "os" - - "github.com/deislabs/duffle/pkg/duffle/home" - "github.com/deislabs/duffle/pkg/signature" - - "github.com/spf13/cobra" -) - -const keyImportDesc = `Import a key or keys to the keyring. - -Add keys to either the public (default) or secret (-s) keyring. By default, this -expects binary keys (in the form generated by 'duffle key export'), but with the -'--armored'/'-a' flag this can take ASCII armored keys as well. - -Keys added to the secret keyring must contain private key material. Keys added to the -public keyring should be public keys, but private keys will be accepted (though the -private key material may be removed). -` - -func newKeyImportCmd(w io.Writer) *cobra.Command { - var secret bool - var armored bool - cmd := &cobra.Command{ - Use: "import FILE", - Short: "import one or more keys to the keyring", - Aliases: []string{"add"}, // This is considered deprecated. - Long: keyImportDesc, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - h := home.Home(homePath()) - var ring string - if secret { - // If secret, add one to the secret keyring as well as the public - // eyring. When added to the public keyring, private material will - // be stripped by `SavePublic`. By doing this, we make sure that - // any key added to the secret ring can be used to verify a - // bundle. - ring = h.SecretKeyRing() - if err := addKeys(args[0], ring, secret, armored); err != nil { - return err - } - } - ring = h.PublicKeyRing() - return addKeys(args[0], ring, false, armored) - }, - } - cmd.Flags().BoolVarP(&secret, "secret", "s", false, "Add a secret (private) key") - cmd.Flags().BoolVarP(&armored, "armored", "a", false, "Load an ASCII armored key") - return cmd -} - -func addKeys(file, ring string, private, armored bool) error { - reader, err := os.Open(file) - if err != nil { - return err - } - defer reader.Close() - kring, err := signature.LoadKeyRing(ring) - if err != nil { - return err - } - kring.PassphraseFetcher = passwordFetcher - if err := kring.Add(reader, armored); err != nil { - return err - } - if private { - return kring.SavePrivate(ring, true) - } - return kring.SavePublic(ring, true, false) -} diff --git a/cmd/duffle/key_list.go b/cmd/duffle/key_list.go deleted file mode 100644 index 580ea8f5..00000000 --- a/cmd/duffle/key_list.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "fmt" - "io" - - "github.com/deislabs/duffle/pkg/duffle/home" - "github.com/deislabs/duffle/pkg/signature" - - "github.com/gosuri/uitable" - "github.com/spf13/cobra" -) - -const keyListDesc = `List key IDs for both public (verify-only) and private (sign and verify) keys. - -By default, this lists both signing and verifying keys. All signing keys can be used -to verify. But a verify-only key can not be used to sign. - -Use the '--signing' flag to list just the signing keys, and the '--verify-only' flag to -list just the public keys, which can only be used for verifying. - -Because a key can exist in both the public and the secret keyring, it is possible for a -key to show up as a signing key with '--signing', and a verifying key with '--verify-only'. -This simply means that the key has been added to both keyrings. -` - -func newKeyListCmd(w io.Writer) *cobra.Command { - var ( - privateOnly bool - publicOnly bool - short bool - ) - cmd := &cobra.Command{ - Use: "list", - Short: "list key IDs", - Long: keyListDesc, - RunE: func(cmd *cobra.Command, args []string) error { - h := home.Home(homePath()) - // Order is important, since duplicate keys are skipped, and a key - // can appear in both keyrings. - rings := []string{h.SecretKeyRing(), h.PublicKeyRing()} - if privateOnly { - rings = []string{h.SecretKeyRing()} - } - if publicOnly { - rings = []string{h.PublicKeyRing()} - } - return listKeys(cmd.OutOrStdout(), short, rings...) - }, - } - cmd.Flags().BoolVarP(&privateOnly, "signing", "s", false, "show private (sign-or-verify) keys") - cmd.Flags().BoolVarP(&publicOnly, "verify-only", "p", false, "show public (verify-only) keys") - cmd.Flags().BoolVar(&short, "short", false, "show less details") - - return cmd -} - -func listKeys(out io.Writer, short bool, rings ...string) error { - kr, err := signature.LoadKeyRings(rings...) - if err != nil { - return err - } - - if short { - for _, k := range kr.Keys() { - name, err := k.UserID() - if err != nil { - fmt.Fprintln(out, "[anonymous key]") - } - fmt.Fprintln(out, name) - } - return nil - } - table := uitable.New() - table.MaxColWidth = 80 - table.Wrap = true - - table.AddRow("NAME", "TYPE", "FINGERPRINT") - for _, k := range kr.Keys() { - var name, fingerprint string - id, err := k.UserID() - if err != nil { - name = "[anonymous key]" - } else { - name = id.String() - } - fingerprint = k.Fingerprint() - typ := "verify-only" - if k.CanSign() { - typ = "signing" - } - table.AddRow(name, typ, fingerprint) - } - fmt.Fprintln(out, table) - return nil -} diff --git a/cmd/duffle/key_list_test.go b/cmd/duffle/key_list_test.go deleted file mode 100644 index 76fd1d5c..00000000 --- a/cmd/duffle/key_list_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "bytes" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestListKeys(t *testing.T) { - out := bytes.NewBuffer(nil) - ring := "../../pkg/signature/testdata/public.gpg" - if err := listKeys(out, true, ring); err != nil { - t.Fatal(err) - } - - is := assert.New(t) - lines := strings.Split(out.String(), "\n") - is.Len(lines, 3) - is.Contains(out.String(), "test2@example.com") - - out.Reset() - if err := listKeys(out, false, ring); err != nil { - t.Fatal(err) - } - lines = strings.Split(out.String(), "\n") - is.Len(lines, 4) - is.Contains(out.String(), "test2@example.com") - is.Contains(out.String(), "FINGERPRINT") -} diff --git a/cmd/duffle/main.go b/cmd/duffle/main.go index c2fce0d2..389cf824 100644 --- a/cmd/duffle/main.go +++ b/cmd/duffle/main.go @@ -8,8 +8,6 @@ import ( "runtime" "strings" - "github.com/deislabs/duffle/pkg/signature" - "github.com/spf13/cobra" "github.com/deislabs/duffle/pkg/bundle" @@ -17,6 +15,8 @@ import ( "github.com/deislabs/duffle/pkg/credentials" "github.com/deislabs/duffle/pkg/driver" "github.com/deislabs/duffle/pkg/duffle/home" + "github.com/deislabs/duffle/pkg/loader" + "github.com/deislabs/duffle/pkg/reference" "github.com/deislabs/duffle/pkg/utils/crud" ) @@ -93,14 +93,6 @@ func loadCredentials(files []string, b *bundle.Bundle) (map[string]string, error return creds, credentials.Validate(creds, b.Credentials) } -// loadVerifyingKeyRings loads all of the keys that can be used for verifying. -// -// This includes all the keys in the public key file and in the secret key file. -func loadVerifyingKeyRings(homedir string) (*signature.KeyRing, error) { - hp := home.Home(homedir) - return signature.LoadKeyRings(hp.PublicKeyRing(), hp.SecretKeyRing()) -} - // isPathy checks to see if a name looks like a path. func isPathy(name string) bool { return strings.Contains(name, string(filepath.Separator)) @@ -130,3 +122,49 @@ func prepareDriver(driverName string) (driver.Driver, error) { return driverImpl, err } + +func loadBundle(bundleFile string) (*bundle.Bundle, error) { + l := loader.NewDetectingLoader() + + // Issue #439: Errors that come back from the loader can be + // pretty opaque. + var bun *bundle.Bundle + if bun, err := l.Load(bundleFile); err != nil { + return bun, fmt.Errorf("cannot load bundle: %s", err) + } + return bun, nil +} +func getReference(bundleName string) (reference.NamedTagged, error) { + var ( + name string + ref reference.NamedTagged + ) + + parts := strings.SplitN(bundleName, "://", 2) + if len(parts) == 2 { + name = parts[1] + } else { + name = parts[0] + } + normalizedRef, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, fmt.Errorf("%q is not a valid bundle name: %v", name, err) + } + if reference.IsNameOnly(normalizedRef) { + ref, err = reference.WithTag(normalizedRef, "latest") + if err != nil { + // NOTE(bacongobbler): Using the default tag *must* be valid. + // To create a NamedTagged type with non-validated + // input, the WithTag function should be used instead. + panic(err) + } + } else { + if taggedRef, ok := normalizedRef.(reference.NamedTagged); ok { + ref = taggedRef + } else { + return nil, fmt.Errorf("unsupported image name: %s", normalizedRef.String()) + } + } + + return ref, nil +} diff --git a/cmd/duffle/pull.go b/cmd/duffle/pull.go deleted file mode 100644 index 786220df..00000000 --- a/cmd/duffle/pull.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io" - "strings" - - "github.com/deislabs/duffle/pkg/bundle" - "github.com/deislabs/duffle/pkg/loader" - "github.com/deislabs/duffle/pkg/reference" - - "github.com/spf13/cobra" -) - -var ErrNotSigned = errors.New("bundle is not signed") - -func newPullCmd(w io.Writer) *cobra.Command { - const usage = `Pulls a CNAB bundle into the cache without installing it. - -Example: - $ duffle pull duffle/example:0.1.0 -` - - var insecure bool - cmd := &cobra.Command{ - Hidden: true, - Use: "pull", - Short: "pull a CNAB bundle from a repository", - Long: usage, - RunE: func(cmd *cobra.Command, args []string) error { - return ErrUnderConstruction - }, - } - - cmd.Flags().BoolVarP(&insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") - - return cmd -} - -func getLoader(home string, insecure bool) (loader.Loader, error) { - var load loader.Loader - if insecure { - load = loader.NewDetectingLoader() - } else { - kr, err := loadVerifyingKeyRings(home) - if err != nil { - return nil, fmt.Errorf("cannot securely load bundle: %s", err) - } - load = loader.NewSecureLoader(kr) - } - return load, nil -} - -func getReference(bundleName string) (reference.NamedTagged, error) { - var ( - name string - ref reference.NamedTagged - ) - - parts := strings.SplitN(bundleName, "://", 2) - if len(parts) == 2 { - name = parts[1] - } else { - name = parts[0] - } - normalizedRef, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, fmt.Errorf("%q is not a valid bundle name: %v", name, err) - } - if reference.IsNameOnly(normalizedRef) { - ref, err = reference.WithTag(normalizedRef, "latest") - if err != nil { - // NOTE(bacongobbler): Using the default tag *must* be valid. - // To create a NamedTagged type with non-validated - // input, the WithTag function should be used instead. - panic(err) - } - } else { - if taggedRef, ok := normalizedRef.(reference.NamedTagged); ok { - ref = taggedRef - } else { - return nil, fmt.Errorf("unsupported image name: %s", normalizedRef.String()) - } - } - - return ref, nil -} - -func loadBundle(bundleFile string, insecure bool) (*bundle.Bundle, error) { - l, err := getLoader(homePath(), insecure) - if err != nil { - return nil, err - } - // Issue #439: Errors that come back from the loader can be - // pretty opaque. - var bun *bundle.Bundle - if bun, err = l.Load(bundleFile); err != nil { - if err.Error() == "no signature block in data" { - return bun, ErrNotSigned - } - // Dear Go, Y U NO TERNARY, kthxbye - secflag := "secure" - if insecure { - secflag = "insecure" - } - return bun, fmt.Errorf("cannot load %s bundle: %s", secflag, err) - } - return bun, nil -} diff --git a/cmd/duffle/push.go b/cmd/duffle/push.go deleted file mode 100644 index 49a3d91e..00000000 --- a/cmd/duffle/push.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "io" - - "github.com/spf13/cobra" -) - -func newPushCmd(out io.Writer) *cobra.Command { - const usage = `Pushes a CNAB bundle to a repository.` - - cmd := &cobra.Command{ - Hidden: true, - Use: "push NAME", - Short: "push a CNAB bundle to a repository", - Long: usage, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - return ErrUnderConstruction - }, - } - - return cmd -} diff --git a/cmd/duffle/root.go b/cmd/duffle/root.go index d1e8b7e8..157a9f23 100644 --- a/cmd/duffle/root.go +++ b/cmd/duffle/root.go @@ -55,7 +55,6 @@ func newRootCmd(outputRedirect io.Writer) *cobra.Command { cmd.AddCommand(newUpgradeCmd(outLog)) cmd.AddCommand(newRunCmd(outLog)) cmd.AddCommand(newCredentialsCmd(outLog)) - cmd.AddCommand(newKeyCmd(outLog)) cmd.AddCommand(newClaimsCmd(outLog)) cmd.AddCommand(newExportCmd(outLog)) cmd.AddCommand(newImportCmd(outLog)) diff --git a/cmd/duffle/show.go b/cmd/duffle/show.go index 9e268b2f..4098c424 100644 --- a/cmd/duffle/show.go +++ b/cmd/duffle/show.go @@ -23,7 +23,6 @@ func newShowCmd(w io.Writer) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&bsc.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") flags.BoolVarP(&bsc.raw, "raw", "r", false, "Display the raw bundle manifest") return cmd diff --git a/cmd/duffle/uninstall.go b/cmd/duffle/uninstall.go index e66b4ee4..95811a1d 100644 --- a/cmd/duffle/uninstall.go +++ b/cmd/duffle/uninstall.go @@ -26,7 +26,6 @@ type uninstallCmd struct { bundle string bundleFile string setParams []string - insecure bool credentialsFiles []string } @@ -54,13 +53,12 @@ func newUninstallCmd(w io.Writer) *cobra.Command { flags.StringVarP(&uninstall.bundle, "bundle", "b", "", "bundle to uninstall") flags.StringVar(&uninstall.bundleFile, "bundle-file", "", "path to a bundle file to uninstall") flags.StringArrayVarP(&uninstall.setParams, "set", "s", []string{}, "set individual parameters as NAME=VALUE pairs") - flags.BoolVarP(&uninstall.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") return cmd } func (un *uninstallCmd) setup() error { - bundleFile, err := prepareBundleFile(un.bundle, un.bundleFile, un.insecure) + bundleFile, err := prepareBundleFile(un.bundle, un.bundleFile) if err != nil { return err } @@ -76,7 +74,7 @@ func (un *uninstallCmd) run() error { } if un.bundleFile != "" { - b, err := loadBundle(un.bundleFile, un.insecure) + b, err := loadBundle(un.bundleFile) if err != nil { return err } diff --git a/cmd/duffle/upgrade.go b/cmd/duffle/upgrade.go index d9ae662a..d9b27f68 100644 --- a/cmd/duffle/upgrade.go +++ b/cmd/duffle/upgrade.go @@ -35,7 +35,6 @@ type upgradeCmd struct { bundle string bundleFile string setParams []string - insecure bool setFiles []string credentialsFiles []string } @@ -65,14 +64,13 @@ func newUpgradeCmd(w io.Writer) *cobra.Command { flags.StringArrayVarP(&upgrade.credentialsFiles, "credentials", "c", []string{}, "Specify credentials to use inside the CNAB bundle. This can be a credentialset name or a path to a file.") flags.StringVarP(&upgrade.valuesFile, "parameters", "p", "", "Specify file containing parameters. Formats: toml, MORE SOON") flags.StringArrayVarP(&upgrade.setParams, "set", "s", []string{}, "Set individual parameters as NAME=VALUE pairs") - flags.BoolVarP(&upgrade.insecure, "insecure", "k", false, "Do not verify the bundle (INSECURE)") flags.StringArrayVarP(&upgrade.setFiles, "set-file", "i", []string{}, "Set individual parameters from file content as NAME=SOURCE-PATH pairs") return cmd } func (up *upgradeCmd) setup() error { - bundleFile, err := prepareBundleFile(up.bundle, up.bundleFile, up.insecure) + bundleFile, err := prepareBundleFile(up.bundle, up.bundleFile) if err != nil { return err } @@ -90,7 +88,7 @@ func (up *upgradeCmd) run() error { // If the user specifies a bundle file, override the existing one. if up.bundleFile != "" { - bun, err := loadBundle(up.bundleFile, up.insecure) + bun, err := loadBundle(up.bundleFile) if err != nil { return err } @@ -129,13 +127,13 @@ func (up *upgradeCmd) run() error { return persistErr } -func prepareBundleFile(bundle, bundleFile string, insecure bool) (string, error) { +func prepareBundleFile(bundle, bundleFile string) (string, error) { if bundle != "" && bundleFile != "" { return "", ErrBundleAndBundleFile } if bundle != "" { - bundleFile, err := getBundleFilepath(bundle, homePath(), insecure) + bundleFile, err := getBundleFilepath(bundle, homePath()) if err != nil { return bundleFile, err } diff --git a/cmd/duffle/upgrade_test.go b/cmd/duffle/upgrade_test.go index 01fea92f..155b1d83 100644 --- a/cmd/duffle/upgrade_test.go +++ b/cmd/duffle/upgrade_test.go @@ -24,6 +24,9 @@ func TestUpgradePersistsClaim(t *testing.T) { // Store a dummy claim instClaim, err := claim.New("foo") + if err != nil { + t.Fatal(err) + } instClaim.Bundle = &bundle.Bundle{ Name: "bar", Version: "0.1.0", @@ -33,9 +36,6 @@ func TestUpgradePersistsClaim(t *testing.T) { }, }, } - if err != nil { - t.Fatal(err) - } err = claimStorage().Store(*instClaim) if err != nil { t.Fatal(err) diff --git a/docs/proposal/204-export-import.md b/docs/proposal/204-export-import.md index 882ed8f4..320d603c 100644 --- a/docs/proposal/204-export-import.md +++ b/docs/proposal/204-export-import.md @@ -5,22 +5,20 @@ This document covers how to export and import a bundle. ## Export Consider the case where a user wants to create package that contains the bundle manifest along with the all of the necessary artifacts to execute the install/uninstall/lifecycle actions specified in the invocation images. You can use the `$ duffle export [BUNDLE_REFERENCE]` command to do just that. -Duffle `export` allows a user to create a gzipped tar archive that contains the bundle manifest (signed or unisgned) along with all of the necessary images including the invocation images and the referenced images in the bundle. See example below +Duffle `export` allows a user to create a gzipped tar archive that contains the bundle manifest along with all of the necessary images including the invocation images and the referenced images in the bundle. See example below ### Export Example ```console $ duffle bundle list -NAME VERSION DIGEST SIGNED? -helloworld 0.1.1 92145d4132aba06e11940a79f20402a3621196f1 true -wordpress 0.2.0 b91550cfc20bd21929e48b21c88715c9e89349eb true +NAME VERSION DIGEST +helloworld 0.1.1 92145d4132aba06e11940a79f20402a3621196f1 +wordpress 0.2.0 b91550cfc20bd21929e48b21c88715c9e89349eb $ duffle export wordpress:0.2.0 $ ls wordpress-0.2.0.tgz ``` -You can pass in the `-k` flag to export an unsigned/insecure bundle or skip verification of a signed bundle. The default behavior (without `-k`) of export is to only package signed bundles after verifying the signature. - In the example, you'll find the exported artifact, a gzipped tar archive: `wordpress-0.2.0.tgz`. Unpacking that artifact results in the following directory structure: ``` wordpress-0.2.0/ @@ -37,7 +35,7 @@ Duffle import is used to import the exported artifact above along with all of th ### Import Example ```console -$ duffle import wordpress-0.2.0.tgz -k +$ duffle import wordpress-0.2.0.tgz $ ls wordpress-0.2.0.tgz wordpress-0.2.0/ diff --git a/pkg/packager/export.go b/pkg/packager/export.go index a453bdec..88aba168 100644 --- a/pkg/packager/export.go +++ b/pkg/packager/export.go @@ -25,14 +25,13 @@ type Exporter struct { Context context.Context Logs string Loader loader.Loader - Unsigned bool } // NewExporter returns an *Exporter given information about where a bundle // lives, where the compressed bundle should be exported to, // and what form a bundle should be exported in (thin or thick/full). It also // sets up a docker client to work with images. -func NewExporter(source, dest, logsDir string, l loader.Loader, thin, unsigned bool) (*Exporter, error) { +func NewExporter(source, dest, logsDir string, l loader.Loader, thin bool) (*Exporter, error) { cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return nil, err @@ -50,7 +49,6 @@ func NewExporter(source, dest, logsDir string, l loader.Loader, thin, unsigned b Context: ctx, Logs: logs, Loader: l, - Unsigned: unsigned, }, nil } @@ -96,10 +94,7 @@ func (ex *Exporter) Export() error { } defer from.Close() - bundlefile := "bundle.cnab" - if ex.Unsigned { - bundlefile = "bundle.json" - } + bundlefile := "bundle.json" to, err := os.OpenFile(filepath.Join(archiveDir, bundlefile), os.O_RDWR|os.O_CREATE, 0666) if err != nil { return err diff --git a/pkg/packager/export_test.go b/pkg/packager/export_test.go index c6badc95..9ffea75d 100644 --- a/pkg/packager/export_test.go +++ b/pkg/packager/export_test.go @@ -1,65 +1,14 @@ package packager import ( - "io" "io/ioutil" "os" "path/filepath" "testing" "github.com/deislabs/duffle/pkg/loader" - "github.com/deislabs/duffle/pkg/signature" ) -func TestExportSigned(t *testing.T) { - testFixtures := filepath.Join("..", "signature", "testdata") - testPublicRing := filepath.Join(testFixtures, "public.gpg") - signedBun := filepath.Join(testFixtures, "signed.json.asc") - - tempDir, err := setupTempDir() - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tempDir) - if err := setupSignedBundle(tempDir, signedBun); err != nil { - t.Fatal(err) - } - kr, err := signature.LoadKeyRing(testPublicRing) - if err != nil { - t.Fatal(err) - } - - tempPWD, pwd, err := setupPWD() - if err != nil { - t.Fatal(err) - } - defer func() { - os.Chdir(pwd) - os.RemoveAll(tempPWD) - }() - - destination := "example-cool-bundle-0.1.0.tgz" - ex := Exporter{ - Source: filepath.Join(tempDir, "bundle.cnab"), - Destination: destination, - Thin: true, - Logs: filepath.Join(tempDir, "export-logs"), - Loader: loader.NewSecureLoader(kr), - Unsigned: false, - } - - if err := ex.Export(); err != nil { - t.Errorf("Expected no error, got error: %v", err) - } - - _, err = os.Stat(destination) - if err != nil && os.IsNotExist(err) { - t.Errorf("Expected %s to exist but was not created", destination) - } else if err != nil { - t.Errorf("Error with compressed bundle file: %v", err) - } -} - func TestExport(t *testing.T) { source, err := filepath.Abs(filepath.Join("testdata", "examplebun", "bundle.json")) if err != nil { @@ -76,11 +25,10 @@ func TestExport(t *testing.T) { }() ex := Exporter{ - Source: source, - Thin: true, - Logs: filepath.Join(tempDir, "export-logs"), - Loader: loader.NewDetectingLoader(), - Unsigned: true, + Source: source, + Thin: true, + Logs: filepath.Join(tempDir, "export-logs"), + Loader: loader.NewDetectingLoader(), } if err := ex.Export(); err != nil { @@ -108,7 +56,6 @@ func TestExportCreatesFileProperly(t *testing.T) { Destination: filepath.Join(tempDir, "random-directory", "examplebun-whatev.tgz"), Thin: true, Logs: filepath.Join(tempDir, "export-logs"), - Unsigned: true, Loader: loader.NewDetectingLoader(), } @@ -133,22 +80,6 @@ func TestExportCreatesFileProperly(t *testing.T) { } } -func setupSignedBundle(tempDir, signedBundle string) error { - from, err := os.Open(signedBundle) - if err != nil { - return err - } - defer from.Close() - to, err := os.OpenFile(filepath.Join(tempDir, "bundle.cnab"), os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - return err - } - defer to.Close() - - _, err = io.Copy(to, from) - return err -} - func setupTempDir() (string, error) { tempDir, err := ioutil.TempDir("", "duffle-export-test") if err != nil {