1// +build go1.8
2
3// Package plugincreds implements a credentials provider sourced from a Go
4// plugin. This package allows you to use a Go plugin to retrieve AWS credentials
5// for the SDK to use for service API calls.
6//
7// As of Go 1.8 plugins are only supported on the Linux platform.
8//
9// Plugin Symbol Name
10//
11// The "GetAWSSDKCredentialProvider" is the symbol name that will be used to
12// lookup the credentials provider getter from the plugin. If you want to use a
13// custom symbol name you should use GetPluginProviderFnsByName to lookup the
14// symbol by a custom name.
15//
16// This symbol is a function that returns two additional functions. One to
17// retrieve the credentials, and another to determine if the credentials have
18// expired.
19//
20// Plugin Symbol Signature
21//
22// The plugin credential provider requires the symbol to match the
23// following signature.
24//
25//   func() (RetrieveFn func() (key, secret, token string, err error), IsExpiredFn func() bool)
26//
27// Plugin Implementation Example
28//
29// The following is an example implementation of a SDK credential provider using
30// the plugin provider in this package. See the SDK's example/aws/credential/plugincreds/plugin
31// folder for a runnable example of this.
32//
33//   package main
34//
35//   func main() {}
36//
37//   var myCredProvider provider
38//
39//   // Build: go build -o plugin.so -buildmode=plugin plugin.go
40//   func init() {
41//   	// Initialize a mock credential provider with stubs
42//   	myCredProvider = provider{"a","b","c"}
43//   }
44//
45//   // GetAWSSDKCredentialProvider is the symbol SDK will lookup and use to
46//   // get the credential provider's retrieve and isExpired functions.
47//   func GetAWSSDKCredentialProvider() (func() (key, secret, token string, err error), func() bool) {
48//   	return myCredProvider.Retrieve,	myCredProvider.IsExpired
49//   }
50//
51//   // mock implementation of a type that returns retrieves credentials and
52//   // returns if they have expired.
53//   type provider struct {
54//   	key, secret, token string
55//   }
56//
57//   func (p provider) Retrieve() (key, secret, token string, err error) {
58//   	return p.key, p.secret, p.token, nil
59//   }
60//
61//   func (p *provider) IsExpired() bool {
62//   	return false;
63//   }
64//
65// Configuring SDK for Plugin Credentials
66//
67// To configure the SDK to use a plugin's credential provider you'll need to first
68// open the plugin file using the plugin standard library package. Once you have
69// a handle to the plugin you can use the NewCredentials function of this package
70// to create a new credentials.Credentials value that can be set as the
71// credentials loader of a Session or Config. See the SDK's example/aws/credential/plugincreds
72// folder for a runnable example of this.
73//
74//   // Open plugin, and load it into the process.
75//   p, err := plugin.Open("somefile.so")
76//   if err != nil {
77//   	return nil, err
78//   }
79//
80//   // Create a new Credentials value which will source the provider's Retrieve
81//   // and IsExpired functions from the plugin.
82//   creds, err := plugincreds.NewCredentials(p)
83//   if err != nil {
84//   	return nil, err
85//   }
86//
87//   // Example to configure a Session with the newly created credentials that
88//   // will be sourced using the plugin's functionality.
89//   sess := session.Must(session.NewSession(&aws.Config{
90//   	Credentials:  creds,
91//   }))
92package plugincreds
93
94import (
95	"fmt"
96	"plugin"
97
98	"github.com/aws/aws-sdk-go/aws/awserr"
99	"github.com/aws/aws-sdk-go/aws/credentials"
100)
101
102// ProviderSymbolName the symbol name the SDK will use to lookup the plugin
103// provider value from.
104const ProviderSymbolName = `GetAWSSDKCredentialProvider`
105
106// ProviderName is the name this credentials provider will label any returned
107// credentials Value with.
108const ProviderName = `PluginCredentialsProvider`
109
110const (
111	// ErrCodeLookupSymbolError failed to lookup symbol
112	ErrCodeLookupSymbolError = "LookupSymbolError"
113
114	// ErrCodeInvalidSymbolError symbol invalid
115	ErrCodeInvalidSymbolError = "InvalidSymbolError"
116
117	// ErrCodePluginRetrieveNil Retrieve function was nil
118	ErrCodePluginRetrieveNil = "PluginRetrieveNilError"
119
120	// ErrCodePluginIsExpiredNil IsExpired Function was nil
121	ErrCodePluginIsExpiredNil = "PluginIsExpiredNilError"
122
123	// ErrCodePluginProviderRetrieve plugin provider's retrieve returned error
124	ErrCodePluginProviderRetrieve = "PluginProviderRetrieveError"
125)
126
127// Provider is the credentials provider that will use the plugin provided
128// Retrieve and IsExpired functions to retrieve credentials.
129type Provider struct {
130	RetrieveFn  func() (key, secret, token string, err error)
131	IsExpiredFn func() bool
132}
133
134// NewCredentials returns a new Credentials loader using the plugin provider.
135// If the symbol isn't found or is invalid in the plugin an error will be
136// returned.
137func NewCredentials(p *plugin.Plugin) (*credentials.Credentials, error) {
138	retrieve, isExpired, err := GetPluginProviderFns(p)
139	if err != nil {
140		return nil, err
141	}
142
143	return credentials.NewCredentials(Provider{
144		RetrieveFn:  retrieve,
145		IsExpiredFn: isExpired,
146	}), nil
147}
148
149// Retrieve will return the credentials Value if they were successfully retrieved
150// from the underlying plugin provider. An error will be returned otherwise.
151func (p Provider) Retrieve() (credentials.Value, error) {
152	creds := credentials.Value{
153		ProviderName: ProviderName,
154	}
155
156	k, s, t, err := p.RetrieveFn()
157	if err != nil {
158		return creds, awserr.New(ErrCodePluginProviderRetrieve,
159			"failed to retrieve credentials with plugin provider", err)
160	}
161
162	creds.AccessKeyID = k
163	creds.SecretAccessKey = s
164	creds.SessionToken = t
165
166	return creds, nil
167}
168
169// IsExpired will return the expired state of the underlying plugin provider.
170func (p Provider) IsExpired() bool {
171	return p.IsExpiredFn()
172}
173
174// GetPluginProviderFns returns the plugin's Retrieve and IsExpired functions
175// returned by the plugin's credential provider getter.
176//
177// Uses ProviderSymbolName as the symbol name when lookup up the symbol. If you
178// want to use a different symbol name, use GetPluginProviderFnsByName.
179func GetPluginProviderFns(p *plugin.Plugin) (func() (key, secret, token string, err error), func() bool, error) {
180	return GetPluginProviderFnsByName(p, ProviderSymbolName)
181}
182
183// GetPluginProviderFnsByName returns the plugin's Retrieve and IsExpired functions
184// returned by the plugin's credential provider getter.
185//
186// Same as GetPluginProviderFns, but takes a custom symbolName to lookup with.
187func GetPluginProviderFnsByName(p *plugin.Plugin, symbolName string) (func() (key, secret, token string, err error), func() bool, error) {
188	sym, err := p.Lookup(symbolName)
189	if err != nil {
190		return nil, nil, awserr.New(ErrCodeLookupSymbolError,
191			fmt.Sprintf("failed to lookup %s plugin provider symbol", symbolName), err)
192	}
193
194	fn, ok := sym.(func() (func() (key, secret, token string, err error), func() bool))
195	if !ok {
196		return nil, nil, awserr.New(ErrCodeInvalidSymbolError,
197			fmt.Sprintf("symbol %T, does not match the 'func() (func() (key, secret, token string, err error), func() bool)'  type", sym), nil)
198	}
199
200	retrieveFn, isExpiredFn := fn()
201	if retrieveFn == nil {
202		return nil, nil, awserr.New(ErrCodePluginRetrieveNil,
203			"the plugin provider retrieve function cannot be nil", nil)
204	}
205	if isExpiredFn == nil {
206		return nil, nil, awserr.New(ErrCodePluginIsExpiredNil,
207			"the plugin provider isExpired function cannot be nil", nil)
208	}
209
210	return retrieveFn, isExpiredFn, nil
211}
212