Skip to content

Commit

Permalink
packer: ensure versions match for remote installs
Browse files Browse the repository at this point in the history
Since we're hardening what Packer is able to load locally when it comes
to plugins, we need also to harden the installation process a bit.

While testing we noticed some remotes had published their plugins with
version mismatches between the tag and the binary.
This was not a problem in the past, as Packer did not care for this,
only the binary name was important, and the plugin could be installed
without problem.

Nowadays however, since Packer enforces the plugin version reported in
the name to be the same as the plugin self-reported version, this makes
it impossible for the installed plugin to load anymore in such an
instance.

Therefore in order to limit confusion, and so users are able to
understand the problem and report it to the plugins with that mismatch,
we reject the installations that expose this mismatch, and report it to
the user if they cannot install anything else.
  • Loading branch information
lbajolet-hashicorp committed Apr 12, 2024
1 parent 40db75f commit 6f12837
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 6 deletions.
54 changes: 54 additions & 0 deletions packer/plugin-getter/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,60 @@ func (pr *Requirement) InstallLatest(opts InstallOptions) (*Installation, error)
return nil, errs
}

// Save binary to temp so we can ensure it is really the version advertised
tempOutput, err := os.CreateTemp("", fmt.Sprintf("packer-plugin*%s", opts.BinaryInstallationOptions.Ext))
if err != nil {
log.Printf("[ERROR] failed to create temp plugin executable: %s", err)
return nil, multierror.Append(errs, err)
}
tempPluginPath := tempOutput.Name()

_, err = io.Copy(tempOutput, copyFrom)
if err != nil {
log.Printf("[ERROR] failed to copy uncompressed binary to %q: %s", tempPluginPath, err)
return nil, multierror.Append(errs, err)
}

err = tempOutput.Chmod(0755)
if err != nil {
log.Printf("[ERROR] failed to change permissions of extracted binary: %s", err)
return nil, multierror.Append(errs, err)
}

// Not a problem on most platforms, but unsure Windows will let us execute an already
// open file, so we close it temporarily to avoid problems
_ = tempOutput.Close()

desc, err := GetPluginDescription(tempPluginPath)
if err != nil {
err := fmt.Errorf("failed to describe plugin binary %q: %s", tempPluginPath, err)
errs = multierror.Append(errs, err)
continue
}

descVersion, err := goversion.NewSemver(desc.Version)
if err != nil {
err := fmt.Errorf("invalid self-reported version %q: %s", desc.Version, err)
errs = multierror.Append(errs, err)
continue
}
if descVersion.Prerelease() != "" {
err := fmt.Errorf("release v%s binary reports version %q, which is unsupported. This is likely a problem with the plugin release workflows.", version, desc.Version)
errs = multierror.Append(errs, err)
continue
}

if descVersion.Compare(version) != 0 {
log.Printf("[ERROR] binary reported version (%q) is different from the expected %q, skipping", desc.Version, version.String())
continue
}

copyFrom, err = os.OpenFile(tempPluginPath, os.O_RDONLY, 0755)
if err != nil {
log.Printf("[ERROR] failed to re-open temporary plugin file %q: %s", tempPluginPath, err)
return nil, multierror.Append(errs, err)
}

outputFile, err := os.OpenFile(outputFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
err := fmt.Errorf("failed to create %s: %w", outputFileName, err)
Expand Down
38 changes: 32 additions & 6 deletions packer/plugin-getter/plugins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -201,12 +202,17 @@ func TestRequirement_InstallLatest(t *testing.T) {
ChecksumFileEntries: map[string][]ChecksumFileEntry{
"2.10.0": {{
Filename: "packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip",
Checksum: "43156b1900dc09b026b54610c4a152edd277366a7f71ff3812583e4a35dd0d4a",
Checksum: "5763f8b5b5ed248894e8511a089cf399b96c7ef92d784fb30ee6242a7cb35bce",
}},
},
Zips: map[string]io.ReadCloser{
"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip": zipFile(map[string]string{
"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": "v2.10.0_x6.0_darwin_amd64",
// Make the false plugin echo an output that matches a subset of `describe` for install to work
//
// Note: this won't work on Windows as they don't have bin/sh, but this will
// eventually be replaced by acceptance tests.
"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": `#!/bin/sh
echo '{"version":"v2.10.0","api_version":"x6.0"}'`,
}),
},
},
Expand Down Expand Up @@ -248,12 +254,17 @@ func TestRequirement_InstallLatest(t *testing.T) {
ChecksumFileEntries: map[string][]ChecksumFileEntry{
"2.10.1": {{
Filename: "packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64.zip",
Checksum: "90ca5b0f13a90238b62581bbf30bacd7e2c9af6592c7f4849627bddbcb039dec",
Checksum: "51451da5cd7f1ecd8699668d806bafe58a9222430842afbefdc62a6698dab260",
}},
},
Zips: map[string]io.ReadCloser{
"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64.zip": zipFile(map[string]string{
"packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64": "v2.10.1_x6.1_darwin_amd64",
// Make the false plugin echo an output that matches a subset of `describe` for install to work
//
// Note: this won't work on Windows as they don't have bin/sh, but this will
// eventually be replaced by acceptance tests.
"packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64": `#!/bin/sh
echo '{"version":"v2.10.1","api_version":"x6.1"}'`,
}),
},
},
Expand Down Expand Up @@ -295,12 +306,17 @@ func TestRequirement_InstallLatest(t *testing.T) {
ChecksumFileEntries: map[string][]ChecksumFileEntry{
"2.10.0": {{
Filename: "packer-plugin-amazon_v2.10.0_x6.1_linux_amd64.zip",
Checksum: "825fc931ae0cb151df0c56be41a17a9136c4d1f1ee73ddb8ed6baa17cef31afa",
Checksum: "5196f57f37e18bfeac10168db6915caae0341bfc4168ebc3d2b959d746cebd0a",
}},
},
Zips: map[string]io.ReadCloser{
"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.1_linux_amd64.zip": zipFile(map[string]string{
"packer-plugin-amazon_v2.10.0_x6.1_linux_amd64": "v2.10.0_x6.1_linux_amd64",
// Make the false plugin echo an output that matches a subset of `describe` for install to work
//
// Note: this won't work on Windows as they don't have bin/sh, but this will
// eventually be replaced by acceptance tests.
"packer-plugin-amazon_v2.10.0_x6.1_linux_amd64": `#!/bin/sh
echo '{"version":"v2.10.0","api_version":"x6.1"}'`,
}),
},
},
Expand Down Expand Up @@ -404,6 +420,16 @@ func TestRequirement_InstallLatest(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
switch tt.name {
case "upgrade-with-diff-protocol-version",
"upgrade-with-same-protocol-version",
"upgrade-with-one-missing-checksum-file":
if runtime.GOOS != "windows" {
break
}
t.Skip("Test %q cannot run on Windows because of a shell script being invoked, skipping.")

Check failure on line 430 in packer/plugin-getter/plugins_test.go

View workflow job for this annotation

GitHub Actions / Darwin go tests

(*testing.common).Skip call has possible Printf formatting directive %q

Check failure on line 430 in packer/plugin-getter/plugins_test.go

View workflow job for this annotation

GitHub Actions / Linux go tests

(*testing.common).Skip call has possible Printf formatting directive %q

Check failure on line 430 in packer/plugin-getter/plugins_test.go

View workflow job for this annotation

GitHub Actions / Windows go tests

(*testing.common).Skip call has possible Printf formatting directive %q
}

log.Printf("starting %s test", tt.name)

identifier, diags := addrs.ParsePluginSourceString("github.com/hashicorp/" + tt.fields.Identifier)
Expand Down

0 comments on commit 6f12837

Please sign in to comment.