150 lines
4.2 KiB
Go
150 lines
4.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
"github.com/hashicorp/terraform/svchost"
|
|
)
|
|
|
|
type helperProgramCredentialsSource struct {
|
|
executable string
|
|
args []string
|
|
}
|
|
|
|
// HelperProgramCredentialsSource returns a CredentialsSource that runs the
|
|
// given program with the given arguments in order to obtain credentials.
|
|
//
|
|
// The given executable path must be an absolute path; it is the caller's
|
|
// responsibility to validate and process a relative path or other input
|
|
// provided by an end-user. If the given path is not absolute, this
|
|
// function will panic.
|
|
//
|
|
// When credentials are requested, the program will be run in a child process
|
|
// with the given arguments along with two additional arguments added to the
|
|
// end of the list: the literal string "get", followed by the requested
|
|
// hostname in ASCII compatibility form (punycode form).
|
|
func HelperProgramCredentialsSource(executable string, args ...string) CredentialsSource {
|
|
if !filepath.IsAbs(executable) {
|
|
panic("NewCredentialsSourceHelperProgram requires absolute path to executable")
|
|
}
|
|
|
|
fullArgs := make([]string, len(args)+1)
|
|
fullArgs[0] = executable
|
|
copy(fullArgs[1:], args)
|
|
|
|
return &helperProgramCredentialsSource{
|
|
executable: executable,
|
|
args: fullArgs,
|
|
}
|
|
}
|
|
|
|
func (s *helperProgramCredentialsSource) ForHost(host svchost.Hostname) (HostCredentials, error) {
|
|
args := make([]string, len(s.args), len(s.args)+2)
|
|
copy(args, s.args)
|
|
args = append(args, "get")
|
|
args = append(args, string(host))
|
|
|
|
outBuf := bytes.Buffer{}
|
|
errBuf := bytes.Buffer{}
|
|
|
|
cmd := exec.Cmd{
|
|
Path: s.executable,
|
|
Args: args,
|
|
Stdin: nil,
|
|
Stdout: &outBuf,
|
|
Stderr: &errBuf,
|
|
}
|
|
err := cmd.Run()
|
|
if _, isExitErr := err.(*exec.ExitError); isExitErr {
|
|
errText := errBuf.String()
|
|
if errText == "" {
|
|
// Shouldn't happen for a well-behaved helper program
|
|
return nil, fmt.Errorf("error in %s, but it produced no error message", s.executable)
|
|
}
|
|
return nil, fmt.Errorf("error in %s: %s", s.executable, errText)
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("failed to run %s: %s", s.executable, err)
|
|
}
|
|
|
|
var m map[string]interface{}
|
|
err = json.Unmarshal(outBuf.Bytes(), &m)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("malformed output from %s: %s", s.executable, err)
|
|
}
|
|
|
|
return HostCredentialsFromMap(m), nil
|
|
}
|
|
|
|
func (s *helperProgramCredentialsSource) StoreForHost(host svchost.Hostname, credentials HostCredentialsWritable) error {
|
|
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 {
|
|
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
|
|
}
|