diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 00fe0ef11..7134a8784 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -114,6 +114,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package") f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true") f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring") + f.StringVar(&client.PassphraseFile, "passphrase-file", "", `location of a file which contains the passphrase for the signing key. Use "-" in order to read from stdin.`) f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version") f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") diff --git a/pkg/action/package.go b/pkg/action/package.go index 0a927cd41..8f53bcac4 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "bufio" "fmt" "io/ioutil" "os" @@ -39,6 +40,7 @@ type Package struct { Sign bool Key string Keyring string + PassphraseFile string Version string AppVersion string Destination string @@ -120,7 +122,15 @@ func (p *Package) Clearsign(filename string) error { return err } - if err := signer.DecryptKey(promptUser); err != nil { + passphraseFetcher := promptUser + if p.PassphraseFile != "" { + passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) + if err != nil { + return err + } + } + + if err := signer.DecryptKey(passphraseFetcher); err != nil { return err } @@ -141,3 +151,34 @@ func promptUser(name string) ([]byte, error) { fmt.Println() return pw, err } + +func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { + file, err := openPassphraseFile(passphraseFile, stdin) + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + passphrase, _, err := reader.ReadLine() + if err != nil { + return nil, err + } + return func(name string) ([]byte, error) { + return passphrase, nil + }, nil +} + +func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { + if passphraseFile == "-" { + stat, err := stdin.Stat() + if err != nil { + return nil, err + } + if (stat.Mode() & os.ModeNamedPipe) == 0 { + return nil, errors.New("specified reading passphrase from stdin, without input on stdin") + } + return stdin, nil + } + return os.Open(passphraseFile) +} diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index 0f716118d..9a202cde4 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -17,8 +17,12 @@ limitations under the License. package action import ( + "io/ioutil" + "os" + "path" "testing" + "helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v3/pkg/chart" ) @@ -42,3 +46,57 @@ func TestSetVersion(t *testing.T) { t.Error("Expected bogus version to return an error.") } } + +func TestPassphraseFileFetcher(t *testing.T) { + secret := "secret" + directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) + defer os.RemoveAll(directory) + + fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + if err != nil { + t.Fatal("Unable to create passphraseFileFetcher", err) + } + + passphrase, err := fetcher("key") + if err != nil { + t.Fatal("Unable to fetch passphrase") + } + + if string(passphrase) != secret { + t.Errorf("Expected %s got %s", secret, string(passphrase)) + } +} + +func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { + secret := "secret" + directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) + defer os.RemoveAll(directory) + + fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) + if err != nil { + t.Fatal("Unable to create passphraseFileFetcher", err) + } + + passphrase, err := fetcher("key") + if err != nil { + t.Fatal("Unable to fetch passphrase") + } + + if string(passphrase) != secret { + t.Errorf("Expected %s got %s", secret, string(passphrase)) + } +} + +func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { + directory := ensure.TempDir(t) + defer os.RemoveAll(directory) + + stdin, err := ioutil.TempFile(directory, "non-existing") + if err != nil { + t.Fatal("Unable to create test file", err) + } + + if _, err := passphraseFileFetcher("-", stdin); err == nil { + t.Error("Expected passphraseFileFetcher returning an error") + } +}