diff --git a/pkg/credentials/credentials.json b/pkg/credentials/credentials.json new file mode 100644 index 0000000000..afbfad559e --- /dev/null +++ b/pkg/credentials/credentials.json @@ -0,0 +1,7 @@ +{ + "Version": 1, + "SessionToken": "token", + "AccessKeyId": "accessKey", + "SecretAccessKey": "secret", + "Expiration": "9999-04-27T16:02:25.000Z" +} diff --git a/pkg/credentials/credentials.sample b/pkg/credentials/credentials.sample index 7fc91d9d20..e2dc1bfecb 100644 --- a/pkg/credentials/credentials.sample +++ b/pkg/credentials/credentials.sample @@ -10,3 +10,6 @@ aws_secret_access_key = secret [with_colon] aws_access_key_id: accessKey aws_secret_access_key: secret + +[with_process] +credential_process = /bin/cat credentials.json diff --git a/pkg/credentials/file_aws_credentials.go b/pkg/credentials/file_aws_credentials.go index ccc8251f4b..196d92614d 100644 --- a/pkg/credentials/file_aws_credentials.go +++ b/pkg/credentials/file_aws_credentials.go @@ -18,18 +18,33 @@ package credentials import ( + "encoding/json" "os" + "os/exec" "path/filepath" + "strings" + "time" homedir "github.com/mitchellh/go-homedir" ini "gopkg.in/ini.v1" ) +// A externalProcessCredentials stores the output of a credential_process +type externalProcessCredentials struct { + Version int + SessionToken string + AccessKeyId string + SecretAccessKey string + Expiration time.Time +} + // A FileAWSCredentials retrieves credentials from the current user's home // directory, and keeps track if those credentials are expired. // // Profile ini file example: $HOME/.aws/credentials type FileAWSCredentials struct { + Expiry + // Path to the shared credentials file. // // If empty will look for "AWS_SHARED_CREDENTIALS_FILE" env variable. If the @@ -90,6 +105,31 @@ func (p *FileAWSCredentials) Retrieve() (Value, error) { // Default to empty string if not found. token := iniProfile.Key("aws_session_token") + // If credential_process is defined, obtain credentials by executing + // the external process + credential_process := iniProfile.Key("credential_process").String() + if credential_process != "" { + args := strings.Fields(credential_process) + cmd := exec.Command(args[0], args[1:]...) + out, err := cmd.Output() + if err != nil { + return Value{}, err + } + var externalProcessCredentials externalProcessCredentials + err = json.Unmarshal([]byte(out), &externalProcessCredentials) + if err != nil { + return Value{}, err + } + p.retrieved = true + p.SetExpiration(externalProcessCredentials.Expiration, DefaultExpiryWindow) + return Value{ + AccessKeyID: externalProcessCredentials.AccessKeyId, + SecretAccessKey: externalProcessCredentials.SecretAccessKey, + SessionToken: externalProcessCredentials.SessionToken, + SignerType: SignatureV4, + }, nil + } + p.retrieved = true return Value{ AccessKeyID: id.String(), @@ -101,7 +141,12 @@ func (p *FileAWSCredentials) Retrieve() (Value, error) { // IsExpired returns if the shared credentials have expired. func (p *FileAWSCredentials) IsExpired() bool { - return !p.retrieved + if p.expiration.IsZero() { + return !p.retrieved + } + + now := time.Now() + return now.After(p.expiration) } // loadProfiles loads from the file pointed to by shared credentials filename for profile. diff --git a/pkg/credentials/file_test.go b/pkg/credentials/file_test.go index 598c3f52ec..d42fd3a51d 100644 --- a/pkg/credentials/file_test.go +++ b/pkg/credentials/file_test.go @@ -120,6 +120,27 @@ func TestFileAWS(t *testing.T) { if !creds.IsExpired() { t.Error("Should be expired if not loaded") } + + os.Clearenv() + + creds = NewFileAWSCredentials("credentials.sample", "with_process") + credValues, err = creds.Get() + if err != nil { + t.Fatal(err) + } + + if credValues.AccessKeyID != "accessKey" { + t.Errorf("Expected 'accessKey', got %s'", credValues.AccessKeyID) + } + if credValues.SecretAccessKey != "secret" { + t.Errorf("Expected 'secret', got %s'", credValues.SecretAccessKey) + } + if credValues.SessionToken != "token" { + t.Errorf("Expected 'token', got %s'", credValues.SessionToken) + } + if creds.IsExpired() { + t.Error("Should not be expired") + } } func TestFileMinioClient(t *testing.T) {