Use AWS SDK to fetch metadata

Previously the EC2 metadata service was straightforward HTTP, but IMDS
v2 now requires managing a session token (and is more secure for it).

We now use the AWS SDK when retrieving metadata; it automatically
supports IMDS v2.
This commit is contained in:
Justin SB 2020-05-31 17:20:12 -04:00
parent f94371075b
commit 7d7b8969ea
1 changed files with 24 additions and 3 deletions

View File

@ -27,6 +27,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/denverdino/aliyungo/oss" "github.com/denverdino/aliyungo/oss"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"google.golang.org/api/option" "google.golang.org/api/option"
@ -69,11 +71,13 @@ func WithBackoff(backoff wait.Backoff) VFSOption {
} }
} }
// ReadLocation reads a file from a vfs URL // ReadFile reads a file from a vfs URL
// It supports additional schemes which don't (yet) have full VFS implementations: // It supports additional schemes which don't (yet) have full VFS implementations:
// metadata: reads from instance metadata on GCE/AWS // metadata: reads from instance metadata on GCE/AWS
// http / https: reads from HTTP // http / https: reads from HTTP
func (c *VFSContext) ReadFile(location string, options ...VFSOption) ([]byte, error) { func (c *VFSContext) ReadFile(location string, options ...VFSOption) ([]byte, error) {
ctx := context.TODO()
var opts vfsOptions var opts vfsOptions
// Exponential backoff, starting with 500 milliseconds, doubling each time, 5 steps // Exponential backoff, starting with 500 milliseconds, doubling each time, 5 steps
opts.backoff = wait.Backoff{ opts.backoff = wait.Backoff{
@ -102,8 +106,7 @@ func (c *VFSContext) ReadFile(location string, options ...VFSOption) ([]byte, er
httpHeaders["Metadata-Flavor"] = "Google" httpHeaders["Metadata-Flavor"] = "Google"
return c.readHTTPLocation(httpURL, httpHeaders, opts) return c.readHTTPLocation(httpURL, httpHeaders, opts)
case "aws": case "aws":
httpURL := "http://169.254.169.254/latest/" + u.Path return c.readAWSMetadata(ctx, u.Path)
return c.readHTTPLocation(httpURL, nil, opts)
case "digitalocean": case "digitalocean":
httpURL := "http://169.254.169.254/metadata/v1" + u.Path httpURL := "http://169.254.169.254/metadata/v1" + u.Path
return c.readHTTPLocation(httpURL, nil, opts) return c.readHTTPLocation(httpURL, nil, opts)
@ -169,6 +172,24 @@ func (c *VFSContext) BuildVfsPath(p string) (Path, error) {
return nil, fmt.Errorf("unknown / unhandled path type: %q", p) return nil, fmt.Errorf("unknown / unhandled path type: %q", p)
} }
// readAWSMetadata reads the specified path from the AWS EC2 metadata service
func (c *VFSContext) readAWSMetadata(ctx context.Context, path string) ([]byte, error) {
awsSession, err := session.NewSession()
if err != nil {
return nil, fmt.Errorf("error building AWS session: %v", err)
}
client := ec2metadata.New(awsSession)
if strings.HasPrefix(path, "/meta-data/") {
s, err := client.GetMetadataWithContext(ctx, strings.TrimPrefix(path, "/meta-data/"))
if err != nil {
return nil, fmt.Errorf("error reading from AWS metadata service: %v", err)
}
return []byte(s), nil
}
// There are others (e.g. user-data), but as we don't use them yet let's not expose them
return nil, fmt.Errorf("unhandled aws metadata path %q", path)
}
// readHTTPLocation reads an http (or https) url. // readHTTPLocation reads an http (or https) url.
// It returns the contents, or an error on any non-200 response. On a 404, it will return os.ErrNotExist // It returns the contents, or an error on any non-200 response. On a 404, it will return os.ErrNotExist
// It will retry a few times on a 500 class error // It will retry a few times on a 500 class error