Skip to content

Commit fd557bb

Browse files
authored
Merge pull request #1356 from bgilbert/region
internal/resource: derive AWS region hint from ARN partition field
2 parents 6465380 + d9c20a1 commit fd557bb

File tree

2 files changed

+105
-67
lines changed

2 files changed

+105
-67
lines changed

internal/resource/url.go

+38-15
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ var (
6363
"Accept-Encoding": []string{"identity"},
6464
"Accept": []string{"application/vnd.coreos.ignition+json;version=3.3.0, */*;q=0.1"},
6565
}
66+
67+
// We could derive this info from aws-sdk-go/aws/endpoints/defaults.go,
68+
// but hardcoding it allows us to unit-test that specific regions
69+
// are used for hinting
70+
awsPartitionRegionHints = map[string]string{
71+
"aws": "us-east-1",
72+
"aws-cn": "cn-north-1",
73+
"aws-us-gov": "us-gov-west-1",
74+
}
6675
)
6776

6877
// Fetcher holds settings for fetching resources from URLs
@@ -409,7 +418,7 @@ func (f *Fetcher) fetchFromS3(u url.URL, dest s3target, opts FetchOptions) error
409418
sess := f.AWSSession.Copy()
410419

411420
// Determine the bucket and key based on the URL scheme
412-
var bucket, key, region string
421+
var bucket, key, region, regionHint string
413422
var err error
414423
switch u.Scheme {
415424
case "s3":
@@ -420,7 +429,7 @@ func (f *Fetcher) fetchFromS3(u url.URL, dest s3target, opts FetchOptions) error
420429
// Parse the bucket and key from the ARN Resource.
421430
// Also set the region for accesspoints.
422431
// S3 bucket ARNs don't include the region field.
423-
bucket, key, region, err = f.parseARN(fullURL)
432+
bucket, key, region, regionHint, err = f.parseARN(fullURL)
424433
if err != nil {
425434
return err
426435
}
@@ -430,14 +439,25 @@ func (f *Fetcher) fetchFromS3(u url.URL, dest s3target, opts FetchOptions) error
430439

431440
// Determine the partition and region this bucket is in
432441
if region == "" {
433-
regionHint := "us-east-1"
434-
if f.S3RegionHint != "" {
442+
// We didn't get an accesspoint ARN, so we don't know the
443+
// region directly. Maybe we computed a region hint from
444+
// the ARN partition field?
445+
if regionHint == "" {
446+
// Nope; we got an unknown ARN partition value or an
447+
// s3:// URL. Maybe we're running in AWS and can
448+
// assume the same partition we're running in?
435449
regionHint = f.S3RegionHint
436450
}
451+
if regionHint == "" {
452+
// Nope; assume aws partition.
453+
regionHint = "us-east-1"
454+
}
455+
// Use the region hint to ask the correct partition for the
456+
// bucket's region.
437457
region, err = s3manager.GetBucketRegion(ctx, sess, bucket, regionHint)
438458
if err != nil {
439459
if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "NotFound" {
440-
return fmt.Errorf("couldn't determine the region for bucket %q: %v", u.Host, err)
460+
return fmt.Errorf("couldn't determine the region for bucket %q: %v", bucket, err)
441461
}
442462
return err
443463
}
@@ -547,21 +567,24 @@ func (f *Fetcher) decompressCopyHashAndVerify(dest io.Writer, src io.Reader, opt
547567
}
548568

549569
// parseARN is a custom wrapper around arn.Parse(); it takes arnURL, a full ARN URL,
550-
// and returns a bucket, a key, a potentially empty region, or an error if the ARN
551-
// is invalid or not for an S3 object.
570+
// and returns a bucket, a key, a potentially empty region, and a
571+
// potentially empty region hint for use in region detection; or an error if
572+
// the ARN is invalid or not for an S3 object.
552573
// If the given arnURL is an accesspoint ARN, the region is set.
553574
// The region is empty for S3 bucket ARNs because they don't include the region field.
554-
func (f *Fetcher) parseARN(arnURL string) (string, string, string, error) {
575+
func (f *Fetcher) parseARN(arnURL string) (string, string, string, string, error) {
555576
if !arn.IsARN(arnURL) {
556-
return "", "", "", configErrors.ErrInvalidS3ARN
577+
return "", "", "", "", configErrors.ErrInvalidS3ARN
557578
}
558579
s3arn, err := arn.Parse(arnURL)
559580
if err != nil {
560-
return "", "", "", err
581+
return "", "", "", "", err
561582
}
562583
if s3arn.Service != "s3" {
563-
return "", "", "", configErrors.ErrInvalidS3ARN
584+
return "", "", "", "", configErrors.ErrInvalidS3ARN
564585
}
586+
// empty if unrecognized partition
587+
regionHint := awsPartitionRegionHints[s3arn.Partition]
565588
// Split the ARN bucket (or accesspoint) and key by separating on slashes.
566589
// See https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths for more info.
567590
urlSplit := strings.Split(arnURL, "/")
@@ -570,7 +593,7 @@ func (f *Fetcher) parseARN(arnURL string) (string, string, string, error) {
570593
if strings.HasPrefix(s3arn.Resource, "accesspoint/") {
571594
// urlSplit must consist of arn, name of accesspoint, and key
572595
if len(urlSplit) < 3 {
573-
return "", "", "", configErrors.ErrInvalidS3ARN
596+
return "", "", "", "", configErrors.ErrInvalidS3ARN
574597
}
575598

576599
// When using GetObjectInput with an access point,
@@ -579,11 +602,11 @@ func (f *Fetcher) parseARN(arnURL string) (string, string, string, error) {
579602
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html
580603
bucket := strings.Join(urlSplit[:2], "/")
581604
key := strings.Join(urlSplit[2:], "/")
582-
return bucket, key, s3arn.Region, nil
605+
return bucket, key, s3arn.Region, regionHint, nil
583606
}
584607
// urlSplit must consist of name of bucket and key
585608
if len(urlSplit) < 2 {
586-
return "", "", "", configErrors.ErrInvalidS3ARN
609+
return "", "", "", "", configErrors.ErrInvalidS3ARN
587610
}
588611

589612
// Parse out the bucket name in order to find the region with s3manager.GetBucketRegion.
@@ -592,5 +615,5 @@ func (f *Fetcher) parseARN(arnURL string) (string, string, string, error) {
592615
bucketUrlSplit := strings.Split(urlSplit[0], ":")
593616
bucket := bucketUrlSplit[len(bucketUrlSplit)-1]
594617
key := strings.Join(urlSplit[1:], "/")
595-
return bucket, key, "", nil
618+
return bucket, key, "", regionHint, nil
596619
}

internal/resource/url_test.go

+67-52
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"reflect"
2222
"testing"
2323

24+
"github.com/stretchr/testify/assert"
25+
2426
"github.com/coreos/ignition/v2/config/shared/errors"
2527
"github.com/coreos/ignition/v2/internal/log"
2628
"github.com/coreos/ignition/v2/internal/util"
@@ -243,50 +245,74 @@ func TestFetchOffline(t *testing.T) {
243245
}
244246

245247
func TestParseARN(t *testing.T) {
246-
type in struct {
247-
url string
248-
}
249-
type out struct {
250-
bucket string
251-
key string
252-
region string
253-
err error
254-
}
255248
tests := []struct {
256-
in in
257-
out out
249+
url string
250+
bucket string
251+
key string
252+
region string
253+
regionHint string
254+
err error
258255
}{
259256
{
260-
in: in{
261-
"arn:aws:iam:us-west-2:123456789012:resource",
262-
},
263-
out: out{
264-
err: errors.ErrInvalidS3ARN,
265-
},
257+
url: "arn:aws:iam:us-west-2:123456789012:resource",
258+
err: errors.ErrInvalidS3ARN,
266259
},
267260
{
268-
in: in{
269-
"arn:aws:s3:::kola-fixtures/resources/anonymous",
270-
},
271-
out: out{
272-
bucket: "kola-fixtures", key: "resources/anonymous",
273-
},
261+
url: "arn:aws:s3:::kola-fixtures/resources/anonymous",
262+
bucket: "kola-fixtures",
263+
key: "resources/anonymous",
264+
regionHint: "us-east-1",
274265
},
275266
{
276-
in: in{
277-
"arn:aws:s3:us-west-2:123456789012:accesspoint/test/object",
278-
},
279-
out: out{
280-
bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint/test", key: "object", region: "us-west-2",
281-
},
267+
url: "arn:aws-cn:s3:::kola-fixtures/resources/anonymous",
268+
bucket: "kola-fixtures",
269+
key: "resources/anonymous",
270+
regionHint: "cn-north-1",
282271
},
283272
{
284-
in: in{
285-
"arn:aws:s3:us-west-2:123456789012:accesspoint/test/path/object",
286-
},
287-
out: out{
288-
bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint/test", key: "path/object", region: "us-west-2",
289-
},
273+
url: "arn:aws-us-gov:s3:::kola-fixtures/resources/anonymous",
274+
bucket: "kola-fixtures",
275+
key: "resources/anonymous",
276+
regionHint: "us-gov-west-1",
277+
},
278+
{
279+
url: "arn:invalid:s3:::kola-fixtures/resources/anonymous",
280+
bucket: "kola-fixtures",
281+
key: "resources/anonymous",
282+
},
283+
{
284+
url: "arn:aws:s3:us-west-2:123456789012:accesspoint/test/object",
285+
bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint/test",
286+
key: "object",
287+
region: "us-west-2",
288+
regionHint: "us-east-1",
289+
},
290+
{
291+
url: "arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint/test/object",
292+
bucket: "arn:aws-cn:s3:cn-northwest-1:123456789012:accesspoint/test",
293+
key: "object",
294+
region: "cn-northwest-1",
295+
regionHint: "cn-north-1",
296+
},
297+
{
298+
url: "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint/test/object",
299+
bucket: "arn:aws-us-gov:s3:us-gov-east-1:123456789012:accesspoint/test",
300+
key: "object",
301+
region: "us-gov-east-1",
302+
regionHint: "us-gov-west-1",
303+
},
304+
{
305+
url: "arn:invalid:s3:us-west-2:123456789012:accesspoint/test/object",
306+
bucket: "arn:invalid:s3:us-west-2:123456789012:accesspoint/test",
307+
key: "object",
308+
region: "us-west-2",
309+
},
310+
{
311+
url: "arn:aws:s3:us-west-2:123456789012:accesspoint/test/path/object",
312+
bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint/test",
313+
key: "path/object",
314+
region: "us-west-2",
315+
regionHint: "us-east-1",
290316
},
291317
}
292318

@@ -296,22 +322,11 @@ func TestParseARN(t *testing.T) {
296322
}
297323

298324
for i, test := range tests {
299-
bucket, key, region, err := f.parseARN(test.in.url)
300-
if !reflect.DeepEqual(test.out.err, err) {
301-
t.Errorf("#%d: expected error %+v, got %+v", i, test.out.err, err)
302-
continue
303-
}
304-
if test.out.err == nil && !reflect.DeepEqual(test.out.bucket, bucket) {
305-
t.Errorf("#%d: expected output %+v, got %+v", i, test.out.bucket, bucket)
306-
continue
307-
}
308-
if test.out.err == nil && !reflect.DeepEqual(test.out.key, key) {
309-
t.Errorf("#%d: expected output %+v, got %+v", i, test.out.key, key)
310-
continue
311-
}
312-
if test.out.err == nil && !reflect.DeepEqual(test.out.region, region) {
313-
t.Errorf("#%d: expected output %+v, got %+v", i, test.out.region, region)
314-
continue
315-
}
325+
bucket, key, region, regionHint, err := f.parseARN(test.url)
326+
assert.Equal(t, test.err, err, "#%d: bad err", i)
327+
assert.Equal(t, test.bucket, bucket, "#%d: bad bucket", i)
328+
assert.Equal(t, test.key, key, "#%d: bad key", i)
329+
assert.Equal(t, test.region, region, "#%d: bad region", i)
330+
assert.Equal(t, test.regionHint, regionHint, "#%d: bad region hint", i)
316331
}
317332
}

0 commit comments

Comments
 (0)