fix(helm): read passphrase from prompt

This prompts the user to enter a passphrase if the given PGP key is
encrypted.

Closes #1447
pull/1460/head
Matt Butcher 8 years ago
parent fce17441f6
commit 9ae97c341c
No known key found for this signature in database
GPG Key ID: DCD5F5E5EF32C345

@ -23,8 +23,10 @@ import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/helm/cmd/helm/helmpath"
"k8s.io/helm/pkg/chartutil"
@ -145,6 +147,10 @@ func (p *packageCmd) clearsign(filename string) error {
return err
}
if err := signer.DecryptKey(promptUser); err != nil {
return err
}
sig, err := signer.ClearSign(filename)
if err != nil {
return err
@ -156,3 +162,11 @@ func (p *packageCmd) clearsign(filename string) error {
return ioutil.WriteFile(filename+".prov", []byte(sig), 0755)
}
// promptUser implements provenance.PassphraseFetcher
func promptUser(name string) ([]byte, error) {
fmt.Printf("Password for key %q > ", name)
pw, err := terminal.ReadPassword(int(syscall.Stdin))
fmt.Println()
return []byte(pw), err
}

@ -27,11 +27,14 @@ This section describes a potential workflow for using provenance data effectivel
Prerequisites:
- A valid, passphrase-less PGP keypair in a binary (not ASCII-armored) format
- A valid PGP keypair in a binary (not ASCII-armored) format
- The `helm` command line tool
- GnuPG command line tools (optional)
- Keybase command line tools (optional)
**NOTE:** If your PGP private key has a passphrase, you will be prompted to enter
that passphrase for any commands that support the `--sign` option.
Creating a new chart is the same as before:
```

@ -144,10 +144,55 @@ func NewFromKeyring(keyringfile, id string) (*Signatory, error) {
if vague {
return s, fmt.Errorf("more than one key contain the id %q", id)
}
s.Entity = candidate
return s, nil
}
// PassphraseFetcher returns a passphrase for decrypting keys.
//
// This is used as a callback to read a passphrase from some other location. The
// given name is the Name field on the key, typically of the form:
//
// USER_NAME (COMMENT) <EMAIL>
type PassphraseFetcher func(name string) ([]byte, error)
// DecryptKey decrypts a private key in the Signatory.
//
// If the key is not encrypted, this will return without error.
//
// If the key does not exist, this will return an error.
//
// If the key exists, but cannot be unlocked with the passphrase returned by
// the PassphraseFetcher, this will return an error.
//
// If the key is successfully unlocked, it will return nil.
func (s *Signatory) DecryptKey(fn PassphraseFetcher) error {
if s.Entity == nil || s.Entity.PrivateKey == nil {
return errors.New("private key not found")
}
// Nothing else to do if key is not encrypted.
if !s.Entity.PrivateKey.Encrypted {
return nil
}
fname := "Unknown"
for i := range s.Entity.Identities {
if i != "" {
fname = i
break
}
}
p, err := fn(fname)
if err != nil {
return err
}
return s.Entity.PrivateKey.Decrypt(p)
}
// ClearSign signs a chart with the given key.
//
// This takes the path to a chart archive file and a key, and it returns a clear signature.

@ -32,6 +32,9 @@ const (
// phrase. Use `gpg --export-secret-keys helm-test` to export the secret.
testKeyfile = "testdata/helm-test-key.secret"
// testPasswordKeyFile is a keyfile with a password.
testPasswordKeyfile = "testdata/helm-password-key.secret"
// testPubfile is the public key file.
// Use `gpg --export helm-test` to export the public key.
testPubfile = "testdata/helm-test-key.pub"
@ -39,6 +42,8 @@ const (
// Generated name for the PGP key in testKeyFile.
testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>`
testPasswordKeyName = `password key (fake) <fake@helm.sh>`
testChartfile = "testdata/hashtest-1.2.3.tgz"
// testSigBlock points to a signature generated by an external tool.
@ -177,6 +182,36 @@ func TestDigestFile(t *testing.T) {
}
}
func TestDecryptKey(t *testing.T) {
k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
if err != nil {
t.Fatal(err)
}
if !k.Entity.PrivateKey.Encrypted {
t.Fatal("Key is not encrypted")
}
// We give this a simple callback that returns the password.
if err := k.DecryptKey(func(s string) ([]byte, error) {
return []byte("secret"), nil
}); err != nil {
t.Fatal(err)
}
// Re-read the key (since we already unlocked it)
k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName)
if err != nil {
t.Fatal(err)
}
// Now we give it a bogus password.
if err := k.DecryptKey(func(s string) ([]byte, error) {
return []byte("secrets_and_lies"), nil
}); err == nil {
t.Fatal("Expected an error when giving a bogus passphrase")
}
}
func TestClearSign(t *testing.T) {
signer, err := NewFromFiles(testKeyfile, testPubfile)
if err != nil {

Loading…
Cancel
Save