-
Notifications
You must be signed in to change notification settings - Fork 493
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
25 changed files
with
10,306 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,270 @@ | ||
package aws | ||
|
||
import ( | ||
"bufio" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"os/user" | ||
"path" | ||
"sync" | ||
"time" | ||
|
||
"bosun.org/_third_party/github.com/vaughan0/go-ini" | ||
) | ||
|
||
// Credentials are used to authenticate and authorize calls that you make to | ||
// AWS. | ||
type Credentials struct { | ||
AccessKeyID string | ||
SecretAccessKey string | ||
SecurityToken string | ||
} | ||
|
||
// A CredentialsProvider is a provider of credentials. | ||
type CredentialsProvider interface { | ||
// Credentials returns a set of credentials (or an error if no credentials | ||
// could be provided). | ||
Credentials() (*Credentials, error) | ||
} | ||
|
||
var ( | ||
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be | ||
// found in the process's environment. | ||
ErrAccessKeyIDNotFound = fmt.Errorf("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment") | ||
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key | ||
// can't be found in the process's environment. | ||
ErrSecretAccessKeyNotFound = fmt.Errorf("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment") | ||
) | ||
|
||
// Context encapsulates the context of a client's connection to an AWS service. | ||
type Context struct { | ||
Service string | ||
Region string | ||
Credentials CredentialsProvider | ||
} | ||
|
||
var currentTime = func() time.Time { | ||
return time.Now() | ||
} | ||
|
||
// DetectCreds returns a CredentialsProvider based on the available information. | ||
// | ||
// If the access key ID and secret access key are provided, it returns a basic | ||
// provider. | ||
// | ||
// If credentials are available via environment variables, it returns an | ||
// environment provider. | ||
// | ||
// If a profile configuration file is available in the default location and has | ||
// a default profile configured, it returns a profile provider. | ||
// | ||
// Otherwise, it returns an IAM instance provider. | ||
func DetectCreds(accessKeyID, secretAccessKey, securityToken string) CredentialsProvider { | ||
if accessKeyID != "" && secretAccessKey != "" { | ||
return Creds(accessKeyID, secretAccessKey, securityToken) | ||
} | ||
|
||
env, err := EnvCreds() | ||
if err == nil { | ||
return env | ||
} | ||
|
||
profile, err := ProfileCreds("", "", 10*time.Minute) | ||
if err != nil { | ||
return IAMCreds() | ||
} | ||
|
||
_, err = profile.Credentials() | ||
if err != nil { | ||
return IAMCreds() | ||
} | ||
|
||
return profile | ||
} | ||
|
||
// EnvCreds returns a static provider of AWS credentials from the process's | ||
// environment, or an error if none are found. | ||
func EnvCreds() (CredentialsProvider, error) { | ||
id := os.Getenv("AWS_ACCESS_KEY_ID") | ||
if id == "" { | ||
id = os.Getenv("AWS_ACCESS_KEY") | ||
} | ||
|
||
secret := os.Getenv("AWS_SECRET_ACCESS_KEY") | ||
if secret == "" { | ||
secret = os.Getenv("AWS_SECRET_KEY") | ||
} | ||
|
||
if id == "" { | ||
return nil, ErrAccessKeyIDNotFound | ||
} | ||
|
||
if secret == "" { | ||
return nil, ErrSecretAccessKeyNotFound | ||
} | ||
|
||
return Creds(id, secret, os.Getenv("AWS_SESSION_TOKEN")), nil | ||
} | ||
|
||
// Creds returns a static provider of credentials. | ||
func Creds(accessKeyID, secretAccessKey, securityToken string) CredentialsProvider { | ||
return staticCredentialsProvider{ | ||
creds: Credentials{ | ||
AccessKeyID: accessKeyID, | ||
SecretAccessKey: secretAccessKey, | ||
SecurityToken: securityToken, | ||
}, | ||
} | ||
} | ||
|
||
// IAMCreds returns a provider which pulls credentials from the local EC2 | ||
// instance's IAM roles. | ||
func IAMCreds() CredentialsProvider { | ||
return &iamProvider{} | ||
} | ||
|
||
// ProfileCreds returns a provider which pulls credentials from the profile | ||
// configuration file. | ||
func ProfileCreds(filename, profile string, expiry time.Duration) (CredentialsProvider, error) { | ||
if filename == "" { | ||
u, err := user.Current() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
filename = path.Join(u.HomeDir, ".aws", "credentials") | ||
} | ||
|
||
if profile == "" { | ||
profile = "default" | ||
} | ||
|
||
return &profileProvider{ | ||
filename: filename, | ||
profile: profile, | ||
expiry: expiry, | ||
}, nil | ||
} | ||
|
||
type profileProvider struct { | ||
filename string | ||
profile string | ||
expiry time.Duration | ||
|
||
creds Credentials | ||
m sync.Mutex | ||
expiration time.Time | ||
} | ||
|
||
func (p *profileProvider) Credentials() (*Credentials, error) { | ||
p.m.Lock() | ||
defer p.m.Unlock() | ||
|
||
if p.expiration.After(currentTime()) { | ||
return &p.creds, nil | ||
} | ||
|
||
config, err := ini.LoadFile(p.filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
profile := config.Section(p.profile) | ||
|
||
accessKeyID, ok := profile["aws_access_key_id"] | ||
if !ok { | ||
return nil, fmt.Errorf("profile %s in %s did not contain aws_access_key_id", p.profile, p.filename) | ||
} | ||
|
||
secretAccessKey, ok := profile["aws_secret_access_key"] | ||
if !ok { | ||
return nil, fmt.Errorf("profile %s in %s did not contain aws_secret_access_key", p.profile, p.filename) | ||
} | ||
|
||
securityToken := profile["aws_session_token"] | ||
|
||
p.creds = Credentials{ | ||
AccessKeyID: accessKeyID, | ||
SecretAccessKey: secretAccessKey, | ||
SecurityToken: securityToken, | ||
} | ||
p.expiration = currentTime().Add(p.expiry) | ||
|
||
return &p.creds, nil | ||
} | ||
|
||
type iamProvider struct { | ||
creds Credentials | ||
m sync.Mutex | ||
expiration time.Time | ||
} | ||
|
||
var metadataCredentialsEndpoint = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" | ||
|
||
// IAMClient is the HTTP client used to query the metadata endpoint for IAM | ||
// credentials. | ||
var IAMClient = http.Client{ | ||
Timeout: 1 * time.Second, | ||
} | ||
|
||
func (p *iamProvider) Credentials() (*Credentials, error) { | ||
p.m.Lock() | ||
defer p.m.Unlock() | ||
|
||
if p.expiration.After(currentTime()) { | ||
return &p.creds, nil | ||
} | ||
|
||
var body struct { | ||
Expiration time.Time | ||
AccessKeyID string | ||
SecretAccessKey string | ||
Token string | ||
} | ||
|
||
resp, err := IAMClient.Get(metadataCredentialsEndpoint) | ||
if err != nil { | ||
return nil, fmt.Errorf("listing IAM credentials") | ||
} | ||
defer func() { | ||
_ = resp.Body.Close() | ||
}() | ||
|
||
// Take the first line of the body of the metadata endpoint | ||
s := bufio.NewScanner(resp.Body) | ||
if !s.Scan() { | ||
return nil, fmt.Errorf("unable to find default IAM credentials") | ||
} else if s.Err() != nil { | ||
return nil, fmt.Errorf("%s listing IAM credentials", s.Err()) | ||
} | ||
|
||
resp, err = IAMClient.Get(metadataCredentialsEndpoint + s.Text()) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting %s IAM credentials", s.Text()) | ||
} | ||
defer func() { | ||
_ = resp.Body.Close() | ||
}() | ||
|
||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { | ||
return nil, fmt.Errorf("decoding %s IAM credentials", s.Text()) | ||
} | ||
|
||
p.creds = Credentials{ | ||
AccessKeyID: body.AccessKeyID, | ||
SecretAccessKey: body.SecretAccessKey, | ||
SecurityToken: body.Token, | ||
} | ||
p.expiration = body.Expiration | ||
|
||
return &p.creds, nil | ||
} | ||
|
||
type staticCredentialsProvider struct { | ||
creds Credentials | ||
} | ||
|
||
func (p staticCredentialsProvider) Credentials() (*Credentials, error) { | ||
return &p.creds, nil | ||
} |
Oops, something went wrong.