svchost/auth: store and forget operations for helper programs
This introduces two new verbs to the credentials helper protocol to store and forget credentials, and uses them to implement StoreForHost and ForgetForHost.
This commit is contained in:
parent
821d0401bc
commit
ec8dadcfa9
|
@ -7,6 +7,8 @@ import (
|
|||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
|
||||
"github.com/hashicorp/terraform/svchost"
|
||||
)
|
||||
|
||||
|
@ -80,9 +82,68 @@ func (s *helperProgramCredentialsSource) ForHost(host svchost.Hostname) (HostCre
|
|||
}
|
||||
|
||||
func (s *helperProgramCredentialsSource) StoreForHost(host svchost.Hostname, credentials HostCredentialsWritable) error {
|
||||
return fmt.Errorf("credentials helper cannot currently store new credentials")
|
||||
args := make([]string, len(s.args), len(s.args)+2)
|
||||
copy(args, s.args)
|
||||
args = append(args, "store")
|
||||
args = append(args, string(host))
|
||||
|
||||
toStore := credentials.ToStore()
|
||||
toStoreRaw, err := ctyjson.Marshal(toStore, toStore.Type())
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't serialize credentials to store: %s", err)
|
||||
}
|
||||
|
||||
inReader := bytes.NewReader(toStoreRaw)
|
||||
errBuf := bytes.Buffer{}
|
||||
|
||||
cmd := exec.Cmd{
|
||||
Path: s.executable,
|
||||
Args: args,
|
||||
Stdin: inReader,
|
||||
Stderr: &errBuf,
|
||||
Stdout: nil,
|
||||
}
|
||||
err = cmd.Run()
|
||||
if _, isExitErr := err.(*exec.ExitError); isExitErr {
|
||||
errText := errBuf.String()
|
||||
if errText == "" {
|
||||
// Shouldn't happen for a well-behaved helper program
|
||||
return fmt.Errorf("error in %s, but it produced no error message", s.executable)
|
||||
}
|
||||
return fmt.Errorf("error in %s: %s", s.executable, errText)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to run %s: %s", s.executable, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *helperProgramCredentialsSource) ForgetForHost(host svchost.Hostname) error {
|
||||
return fmt.Errorf("credentials helper cannot currently forget existing credentials")
|
||||
args := make([]string, len(s.args), len(s.args)+2)
|
||||
copy(args, s.args)
|
||||
args = append(args, "forget")
|
||||
args = append(args, string(host))
|
||||
|
||||
errBuf := bytes.Buffer{}
|
||||
|
||||
cmd := exec.Cmd{
|
||||
Path: s.executable,
|
||||
Args: args,
|
||||
Stdin: nil,
|
||||
Stderr: &errBuf,
|
||||
Stdout: nil,
|
||||
}
|
||||
err := cmd.Run()
|
||||
if _, isExitErr := err.(*exec.ExitError); isExitErr {
|
||||
errText := errBuf.String()
|
||||
if errText == "" {
|
||||
// Shouldn't happen for a well-behaved helper program
|
||||
return fmt.Errorf("error in %s, but it produced no error message", s.executable)
|
||||
}
|
||||
return fmt.Errorf("error in %s: %s", s.executable, errText)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to run %s: %s", s.executable, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -56,4 +56,28 @@ func TestHelperProgramCredentialsSource(t *testing.T) {
|
|||
t.Error("completed successfully; want error")
|
||||
}
|
||||
})
|
||||
t.Run("store happy path", func(t *testing.T) {
|
||||
err := src.StoreForHost(svchost.Hostname("example.com"), HostCredentialsToken("example-token"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("store error", func(t *testing.T) {
|
||||
err := src.StoreForHost(svchost.Hostname("fail.example.com"), HostCredentialsToken("example-token"))
|
||||
if err == nil {
|
||||
t.Error("completed successfully; want error")
|
||||
}
|
||||
})
|
||||
t.Run("forget happy path", func(t *testing.T) {
|
||||
err := src.ForgetForHost(svchost.Hostname("example.com"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("forget error", func(t *testing.T) {
|
||||
err := src.ForgetForHost(svchost.Hostname("fail.example.com"))
|
||||
if err == nil {
|
||||
t.Error("completed successfully; want error")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
@ -15,21 +17,44 @@ func main() {
|
|||
die("not enough arguments\n")
|
||||
}
|
||||
|
||||
if args[1] != "get" {
|
||||
die("unknown subcommand %q\n", args[1])
|
||||
}
|
||||
|
||||
host := args[2]
|
||||
switch args[1] {
|
||||
case "get":
|
||||
switch host {
|
||||
case "example.com":
|
||||
fmt.Print(`{"token":"example-token"}`)
|
||||
case "other-cred-type.example.com":
|
||||
fmt.Print(`{"username":"alfred"}`) // unrecognized by main program
|
||||
case "fail.example.com":
|
||||
die("failing because you told me to fail\n")
|
||||
default:
|
||||
fmt.Print("{}") // no credentials available
|
||||
}
|
||||
case "store":
|
||||
dataSrc, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
die("invalid input: %s", err)
|
||||
}
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(dataSrc, &data)
|
||||
|
||||
switch host {
|
||||
case "example.com":
|
||||
fmt.Print(`{"token":"example-token"}`)
|
||||
case "other-cred-type.example.com":
|
||||
fmt.Print(`{"username":"alfred"}`) // unrecognized by main program
|
||||
case "fail.example.com":
|
||||
die("failing because you told me to fail\n")
|
||||
switch host {
|
||||
case "example.com":
|
||||
if data["token"] != "example-token" {
|
||||
die("incorrect token value to store")
|
||||
}
|
||||
default:
|
||||
die("can't store credentials for %s", host)
|
||||
}
|
||||
case "forget":
|
||||
switch host {
|
||||
case "example.com":
|
||||
// okay!
|
||||
default:
|
||||
die("can't forget credentials for %s", host)
|
||||
}
|
||||
default:
|
||||
fmt.Print("{}") // no credentials available
|
||||
die("unknown subcommand %q\n", args[1])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue