-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
aws/credentials/plugincreds: Add support for Go plugin for credentials (
#1320) Adds support for using plugins to retrieve credentials for API requests. This change adds a new package plugincreds under aws/credentials. As of Go 1.8 this functionality is only available for Linux. See the example/aws/credentials/plugincreds folder in the SDK for example usage.
- Loading branch information
Showing
6 changed files
with
415 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// +build !go1.8 | ||
|
||
// Package plugincreds provides usage of Go plugins for providing credentials | ||
// to the SDK. Only available with Go 1.8 and above. | ||
package plugincreds |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// +build go1.8 | ||
|
||
// Package plugincreds implements a credentials provider sourced from a Go | ||
// plugin. This package allows you to use a Go plugin to retrieve AWS credentials | ||
// for the SDK to use for service API calls. | ||
// | ||
// As of Go 1.8 plugins are only supported on the Linux platform. | ||
// | ||
// Plugin Symbol Name | ||
// | ||
// See ProviderSymbolName for the symbol named that will be used to lookup the | ||
// credentials plugin provider. If you want to use a custom symbol name you | ||
// should use GetPluginProviderFnsByName to lookup the symbol by a custom name. | ||
// | ||
// This symbol is a function that returns two additional functions. One to | ||
// retrieve the credentials, and another to determine if the credentials have | ||
// expired. | ||
// | ||
// Plugin Symbol Signature | ||
// | ||
// The plugin credential provider requires the symbol to match the | ||
// following signature. | ||
// | ||
// func() (RetrieveFn func() (key, secret, token string, err error), IsExpiredFn func() bool) | ||
// | ||
// Plugin Implementation Exmaple | ||
// | ||
// The following is an example implementation of a SDK credential provider using | ||
// the plugin provider in this package. See the SDK's example/aws/credential/plugincreds/plugin | ||
// folder for a runnable example of this. | ||
// | ||
// package main | ||
// | ||
// func main() {} | ||
// | ||
// var myCredProvider provider | ||
// | ||
// // Build: go build -o plugin.so -buildmode=plugin plugin.go | ||
// func init() { | ||
// // Initialize a mock credential provider with stubs | ||
// myCredProvider = provider{"a","b","c"} | ||
// } | ||
// | ||
// // GetAWSSDKCredentialProvider is the symbol SDK will lookup and use to | ||
// // get the credential provider's retrieve and isExpired functions. | ||
// func GetAWSSDKCredentialProvider() (func() (key, secret, token string, err error), func() bool) { | ||
// return myCredProvider.Retrieve, myCredProvider.IsExpired | ||
// } | ||
// | ||
// // mock implementation of a type that returns retrieves credentials and | ||
// // returns if they have expired. | ||
// type provider struct { | ||
// key, secret, token string | ||
// } | ||
// | ||
// func (p provider) Retrieve() (key, secret, token string, err error) { | ||
// return p.key, p.secret, p.token, nil | ||
// } | ||
// | ||
// func (p *provider) IsExpired() bool { | ||
// return false; | ||
// } | ||
// | ||
// Configuring SDK for Plugin Credentials | ||
// | ||
// To configure the SDK to use a plugin's credential provider you'll need to first | ||
// open the plugin file using the plugin standard library package. Once you have | ||
// a handle to the plugin you can use the NewCredentials function of this package | ||
// to create a new credentials.Credentials value that can be set as the | ||
// credentials loader of a Session or Config. See the SDK's example/aws/credential/plugincreds | ||
// folder for a runnable example of this. | ||
// | ||
// // Open plugin, and load it into the process. | ||
// p, err := plugin.Open("somefile.so") | ||
// if err != nil { | ||
// return nil, err | ||
// } | ||
// | ||
// // Create a new Credentials value which will source the provider's Retrieve | ||
// // and IsExpired functions from the plugin. | ||
// creds, err := plugincreds.NewCredentials(p) | ||
// if err != nil { | ||
// return nil, err | ||
// } | ||
// | ||
// // Example to configure a Session with the newly created credentials that | ||
// // will be sourced using the plugin's functionality. | ||
// sess := session.Must(session.NewSession(&aws.Config{ | ||
// Credentials: creds, | ||
// })) | ||
package plugincreds | ||
|
||
import ( | ||
"fmt" | ||
"plugin" | ||
|
||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/aws/aws-sdk-go/aws/credentials" | ||
) | ||
|
||
// ProviderSymbolName the symbol name the SDK will use to lookup the plugin | ||
// provider value from. | ||
const ProviderSymbolName = `GetAWSSDKCredentialProvider` | ||
|
||
// ProviderName is the name this credentials provider will label any returned | ||
// credentials Value with. | ||
const ProviderName = `PluginCredentialsProvider` | ||
|
||
const ( | ||
// ErrCodeLookupSymbolError failed to lookup symbol | ||
ErrCodeLookupSymbolError = "LookupSymbolError" | ||
|
||
// ErrCodeInvalidSymbolError symbol invalid | ||
ErrCodeInvalidSymbolError = "InvalidSymbolError" | ||
|
||
// ErrCodePluginRetrieveNil Retrieve function was nil | ||
ErrCodePluginRetrieveNil = "PluginRetrieveNilError" | ||
|
||
// ErrCodePluginIsExpiredNil IsExpired Function was nil | ||
ErrCodePluginIsExpiredNil = "PluginIsExpiredNilError" | ||
|
||
// ErrCodePluginProviderRetrieve plugin provider's retrieve returned error | ||
ErrCodePluginProviderRetrieve = "PluginProviderRetrieveError" | ||
) | ||
|
||
// Provider is the credentials provider that will use the plugin provided | ||
// Retrieve and IsExpired functions to retrieve credentials. | ||
type Provider struct { | ||
RetrieveFn func() (key, secret, token string, err error) | ||
IsExpiredFn func() bool | ||
} | ||
|
||
// NewCredentials returns a new Credentials loader using the plugin provider. | ||
// If the symbol isn't found or is invalid in the plugin an error will be | ||
// returned. | ||
func NewCredentials(p *plugin.Plugin) (*credentials.Credentials, error) { | ||
retrieve, isExpired, err := GetPluginProviderFns(p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return credentials.NewCredentials(Provider{ | ||
RetrieveFn: retrieve, | ||
IsExpiredFn: isExpired, | ||
}), nil | ||
} | ||
|
||
// Retrieve will return the credentials Value if they were successfully retrieved | ||
// from the underlying plugin provider. An error will be returned otherwise. | ||
func (p Provider) Retrieve() (credentials.Value, error) { | ||
creds := credentials.Value{ | ||
ProviderName: ProviderName, | ||
} | ||
|
||
k, s, t, err := p.RetrieveFn() | ||
if err != nil { | ||
return creds, awserr.New(ErrCodePluginProviderRetrieve, | ||
"failed to retrieve credentials with plugin provider", err) | ||
} | ||
|
||
creds.AccessKeyID = k | ||
creds.SecretAccessKey = s | ||
creds.SessionToken = t | ||
|
||
return creds, nil | ||
} | ||
|
||
// IsExpired will return the expired state of the underlying plugin provider. | ||
func (p Provider) IsExpired() bool { | ||
return p.IsExpiredFn() | ||
} | ||
|
||
// GetPluginProviderFns returns the plugin's Retrieve and IsExpired functions | ||
// returned by the plugin's credential provider getter. | ||
// | ||
// Uses ProviderSymbolName as the symbol name when lookup up the symbol. If you | ||
// want to use a different symbol name, use GetPluginProviderFnsByName. | ||
func GetPluginProviderFns(p *plugin.Plugin) (func() (key, secret, token string, err error), func() bool, error) { | ||
return GetPluginProviderFnsByName(p, ProviderSymbolName) | ||
} | ||
|
||
// GetPluginProviderFnsByName returns the plugin's Retrieve and IsExpired functions | ||
// returned by the plugin's credential provider getter. | ||
// | ||
// Same as GetPluginProviderFns, but takes a custom symbolName to lookup with. | ||
func GetPluginProviderFnsByName(p *plugin.Plugin, symbolName string) (func() (key, secret, token string, err error), func() bool, error) { | ||
sym, err := p.Lookup(symbolName) | ||
if err != nil { | ||
return nil, nil, awserr.New(ErrCodeLookupSymbolError, | ||
fmt.Sprintf("failed to lookup %s plugin provider symbol", symbolName), err) | ||
} | ||
|
||
fn, ok := sym.(func() (func() (key, secret, token string, err error), func() bool)) | ||
if !ok { | ||
return nil, nil, awserr.New(ErrCodeInvalidSymbolError, | ||
fmt.Sprintf("symbol %T, does not match the 'func() (func() (key, secret, token string, err error), func() bool)' type", sym), nil) | ||
} | ||
|
||
retrieveFn, isExpiredFn := fn() | ||
if retrieveFn == nil { | ||
return nil, nil, awserr.New(ErrCodePluginRetrieveNil, | ||
"the plugin provider retrieve function cannot be nil", nil) | ||
} | ||
if isExpiredFn == nil { | ||
return nil, nil, awserr.New(ErrCodePluginIsExpiredNil, | ||
"the plugin provider isExpired function cannot be nil", nil) | ||
} | ||
|
||
return retrieveFn, isExpiredFn, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// +build go1.8 | ||
|
||
package plugincreds | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go/aws/awserr" | ||
"github.com/aws/aws-sdk-go/aws/credentials" | ||
) | ||
|
||
func TestProvider_Passthrough(t *testing.T) { | ||
p := Provider{ | ||
RetrieveFn: func() (string, string, string, error) { | ||
return "key", "secret", "token", nil | ||
}, | ||
IsExpiredFn: func() bool { | ||
return false | ||
}, | ||
} | ||
|
||
actual, err := p.Retrieve() | ||
if err != nil { | ||
t.Fatalf("expect no error, got %v", err) | ||
} | ||
|
||
expect := credentials.Value{ | ||
AccessKeyID: "key", | ||
SecretAccessKey: "secret", | ||
SessionToken: "token", | ||
ProviderName: ProviderName, | ||
} | ||
if expect != actual { | ||
t.Errorf("expect %+v credentials, got %+v", expect, actual) | ||
} | ||
} | ||
|
||
func TestProvider_Error(t *testing.T) { | ||
expectErr := fmt.Errorf("expect error") | ||
|
||
p := Provider{ | ||
RetrieveFn: func() (string, string, string, error) { | ||
return "", "", "", expectErr | ||
}, | ||
IsExpiredFn: func() bool { | ||
return false | ||
}, | ||
} | ||
|
||
actual, err := p.Retrieve() | ||
if err == nil { | ||
t.Fatalf("expect error, got none") | ||
} | ||
|
||
aerr := err.(awserr.Error) | ||
if e, a := ErrCodePluginProviderRetrieve, aerr.Code(); e != a { | ||
t.Errorf("expect %s error code, got %s", e, a) | ||
} | ||
|
||
if e, a := expectErr, aerr.OrigErr(); e != a { | ||
t.Errorf("expect %v cause error, got %v", e, a) | ||
} | ||
|
||
expect := credentials.Value{ | ||
ProviderName: ProviderName, | ||
} | ||
if expect != actual { | ||
t.Errorf("expect %+v credentials, got %+v", expect, actual) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
Retrieve Credentials with Go Plugin | ||
=== | ||
|
||
This example demonstrates how you can take advantage of Go 1.8's new Plugin | ||
functionality to retrieve AWS credentials dynamically from a plugin compiled | ||
separate from your application. | ||
|
||
Usage | ||
--- | ||
|
||
Example Plugin | ||
--- | ||
|
||
You can find the plugin at `plugin/plugin.go` nested within this example. The plugin | ||
demonstrates what symbol the SDK will use when lookup up the credential provider | ||
and the type signature that needs to be implemented. | ||
|
||
Compile the plugin with: | ||
|
||
go build -tags example -o plugin.so -buildmode=plugin plugin.go | ||
|
||
Example Application | ||
--- | ||
|
||
The `main.go` file in this folder demonstrates how you can configure the SDK to | ||
use a plugin to retrieve credentials with. | ||
|
||
Compile and run application: | ||
|
||
go build -tags example -o usePlugin usePlugin.go | ||
|
||
./usePlugin ./plugin/plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// +build example,go18 | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"plugin" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/credentials/plugincreds" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/s3" | ||
) | ||
|
||
// Example application which loads a Go Plugin file, and uses the credential | ||
// provider defined within the plugin to get credentials for making a S3 | ||
// request. | ||
// | ||
// Build: | ||
// go build -tags example -o usePlugin main.go | ||
// | ||
// Usage: | ||
// ./usePlugin <compiled plugin> | ||
func main() { | ||
if len(os.Args) < 2 { | ||
exitErrorPrintf("Usage: usePlugin <compiled plugin>") | ||
} | ||
|
||
// Open plugin, and load it into the process. | ||
p, err := plugin.Open(os.Args[1]) | ||
if err != nil { | ||
exitErrorPrintf("failed to open plugin, %s, %v", os.Args[1], err) | ||
} | ||
|
||
// Create a new Credentials value which will source the provider's Retrieve | ||
// and IsExpired functions from the plugin. | ||
creds, err := plugincreds.NewCredentials(p) | ||
if err != nil { | ||
exitErrorPrintf("failed to load plugin provider, %v", err) | ||
} | ||
|
||
// Example to configure a Session with the newly created credentials that | ||
// will be sourced using the plugin's functionality. | ||
sess := session.Must(session.NewSession(&aws.Config{ | ||
Credentials: creds, | ||
})) | ||
|
||
svc := s3.New(sess) | ||
result, err := svc.HeadObject(&s3.HeadObjectInput{ | ||
Bucket: aws.String("myBucket"), | ||
Key: aws.String("myKey"), | ||
}) | ||
|
||
fmt.Println(result, err) | ||
} | ||
|
||
func exitErrorPrintf(format string, args ...interface{}) { | ||
fmt.Fprintf(os.Stderr, format, args...) | ||
os.Exit(1) | ||
} |
Oops, something went wrong.