15599: Look up IAM role name from metadata if not configured.
authorTom Clegg <tclegg@veritasgenetics.com>
Fri, 27 Sep 2019 21:30:47 +0000 (17:30 -0400)
committerTom Clegg <tclegg@veritasgenetics.com>
Mon, 30 Sep 2019 23:03:02 +0000 (19:03 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

doc/install/configure-s3-object-storage.html.textile.liquid
services/keepstore/s3_volume.go

index 4007e6d221784368093cb91dc5ff41a1152be73f..d6366aa6abfabc0a2261b9a0fede2858551b282e 100644 (file)
@@ -32,13 +32,16 @@ Volumes are configured in the @Volumes@ section of the cluster configuration fil
 
         Driver: S3
         DriverParameters:
-          # If an IAM role name is given here, credentials to access
-          # the bucket will be retrieved at runtime from instance
-          # metadata.
+          # IAM role name to use when retrieving credentials from
+          # instance metadata. This is optional (if omitted, the role
+          # name itself is retrieved from instance metadata) but it
+          # may protect you from using the wrong credentials in the
+          # event of an installation/configuration error.
           IAMRole: s3access
 
           # The credentials to use to access the bucket. Omit or leave
-          # blank if IAMRole is configured.
+          # blank to use the credentials provided by the instance's
+          # IAM role.
           AccessKey: aaaaa
           SecretKey: aaaaa
 
index ad04a2e2229036fc945b218968cbbdae26e73aa7..20bd99255a33659f8ee6f774bea5be73e140732b 100644 (file)
@@ -48,9 +48,6 @@ func (v *S3Volume) check() error {
        if v.Bucket == "" {
                return errors.New("DriverParameters: Bucket must be provided")
        }
-       if v.IAMRole == "" && (v.AccessKey == "" || v.SecretKey == "") {
-               return errors.New("DriverParameters: either IAMRole or literal credentials (AccessKey and SecretKey) must be provided")
-       }
        if v.IndexPageSize == 0 {
                v.IndexPageSize = 1000
        }
@@ -161,7 +158,7 @@ func (v *S3Volume) GetDeviceID() string {
 }
 
 func (v *S3Volume) bootstrapIAMCredentials() error {
-       if v.IAMRole == "" {
+       if v.AccessKey != "" || v.SecretKey != "" {
                return nil
        }
        ttl, err := v.updateIAMCredentials()
@@ -213,15 +210,40 @@ func (v *S3Volume) updateIAMCredentials() (time.Duration, error) {
        ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
        defer cancel()
 
+       metadataBaseURL := "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
+
        var url string
        if strings.Contains(v.IAMRole, "://") {
                // Configuration provides complete URL (used by tests)
                url = v.IAMRole
-       } else {
+       } else if v.IAMRole != "" {
                // Configuration provides IAM role name and we use the
                // AWS metadata endpoint
-               url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" + v.IAMRole
+               url = metadataBaseURL + v.IAMRole
+       } else {
+               url = metadataBaseURL
+               v.logger.WithField("URL", url).Debug("looking up IAM role name")
+               req, err := http.NewRequest("GET", url, nil)
+               if err != nil {
+                       return 0, fmt.Errorf("error setting up request %s: %s", url, err)
+               }
+               resp, err := http.DefaultClient.Do(req.WithContext(ctx))
+               if err != nil {
+                       return 0, fmt.Errorf("error getting %s: %s", url, err)
+               }
+               defer resp.Body.Close()
+               if resp.StatusCode != http.StatusOK {
+                       return 0, fmt.Errorf("error getting %s: HTTP status %s", url, resp.Status)
+               }
+               var role string
+               _, err = fmt.Fscanf(resp.Body, "%s\n", &role)
+               if err != nil {
+                       return 0, fmt.Errorf("error reading response from %s: %s", url, err)
+               }
+               v.logger.WithField("Role", role).Debug("looked up IAM role name")
+               url = url + role
        }
+
        v.logger.WithField("URL", url).Debug("getting credentials")
        req, err := http.NewRequest("GET", url, nil)
        if err != nil {