Skip to content

Commit 685ff9b

Browse files
authored
Merge pull request #9 from aws-samples/aksk
fix:support access key rotation
2 parents 18b6fdb + bf0d450 commit 685ff9b

7 files changed

Lines changed: 144 additions & 17 deletions

File tree

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ Supported types:
199199
ctx := context.Background()
200200

201201
var job dth.Job
202-
203202
switch jobType {
204203
case "Finder":
205204
job = dth.NewFinder(ctx, cfg)
@@ -211,6 +210,7 @@ Supported types:
211210
log.Fatalf("Unknown Job Type - %s. Type must be either Finder or Worker\n, please start again", jobType)
212211

213212
}
213+
go dth.RunTicker(ctx, cfg)
214214
job.Run(ctx)
215215
},
216216
}

dth/client.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Client interface {
5959
// S3Client is an implementation of Client interface for Amazon S3
6060
type S3Client struct {
6161
bucket, prefix, prefixList, region, sourceType string
62+
isSrcClient bool
6263
client *s3.Client
6364
}
6465

@@ -85,6 +86,35 @@ func getEndpointURL(region, sourceType string) (url string) {
8586
return url
8687
}
8788

89+
func getClientCredentialsModifyFn(isSrc bool, src_cred, dst_cred *S3Credentials) func(o *s3.Options) {
90+
if isSrc {
91+
return func(o *s3.Options) {
92+
if src_cred == nil {
93+
log.Print("src_cred is nil, no modification")
94+
return
95+
}
96+
if src_cred.noSignRequest {
97+
o.Credentials = aws.AnonymousCredentials{}
98+
}
99+
if src_cred.accessKey != "" {
100+
o.Credentials = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(src_cred.accessKey, src_cred.secretKey, ""))
101+
}
102+
}
103+
}
104+
return func(o *s3.Options) {
105+
if dst_cred == nil {
106+
log.Print("dst_cred is nil, no modification")
107+
return
108+
}
109+
if dst_cred.noSignRequest {
110+
o.Credentials = aws.AnonymousCredentials{}
111+
}
112+
if dst_cred.accessKey != "" {
113+
o.Credentials = aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(dst_cred.accessKey, dst_cred.secretKey, ""))
114+
}
115+
}
116+
}
117+
88118
// NewS3Client creates a S3Client instance
89119
func NewS3Client(ctx context.Context, bucket, prefix, prefixList, endpoint, region, sourceType string, cred *S3Credentials) *S3Client {
90120

@@ -147,7 +177,7 @@ func (c *S3Client) GetObject(ctx context.Context, key *string, size, start, chun
147177
Range: &bodyRange,
148178
}
149179

150-
output, err := c.client.GetObject(ctx, input)
180+
output, err := c.client.GetObject(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
151181
if err != nil {
152182
log.Printf("S3> Unable to download %s with %d bytes start from %d - %s\n", *key, size, start, err.Error())
153183
return nil, err
@@ -170,10 +200,10 @@ func (c *S3Client) GetObject(ctx context.Context, key *string, size, start, chun
170200
func (c *S3Client) listObjectFn(ctx context.Context, continuationToken, prefix, delimiter *string, maxKeys int32) (*s3.ListObjectsV2Output, error) {
171201

172202
input := &s3.ListObjectsV2Input{
173-
Bucket: &c.bucket,
174-
Prefix: prefix,
175-
MaxKeys: maxKeys,
176-
Delimiter: delimiter,
203+
Bucket: &c.bucket,
204+
Prefix: prefix,
205+
MaxKeys: maxKeys,
206+
Delimiter: delimiter,
177207
EncodingType: "url",
178208
}
179209

@@ -182,7 +212,7 @@ func (c *S3Client) listObjectFn(ctx context.Context, continuationToken, prefix,
182212
}
183213

184214
// start := time.Now()
185-
output, err := c.client.ListObjectsV2(ctx, input)
215+
output, err := c.client.ListObjectsV2(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
186216
if err != nil {
187217
log.Printf("Unable to list objects in /%s - %s\n", *prefix, err.Error())
188218
return nil, err
@@ -305,7 +335,7 @@ func (c *S3Client) HeadObject(ctx context.Context, key *string) *Metadata {
305335
Key: key,
306336
}
307337

308-
output, err := c.client.HeadObject(ctx, input)
338+
output, err := c.client.HeadObject(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
309339
if err != nil {
310340
log.Printf("Failed to head object for %s - %s\n", *key, err.Error())
311341
return nil
@@ -324,7 +354,9 @@ func (c *S3Client) HeadObject(ctx context.Context, key *string) *Metadata {
324354
// ListSelectedPrefixes is a function to list prefixes from a customized list file.
325355
func (c *S3Client) ListSelectedPrefixes(ctx context.Context, key *string) (prefixes []*string) {
326356

327-
downloader := manager.NewDownloader(c.client)
357+
downloader := manager.NewDownloader(c.client, func(d *manager.Downloader) {
358+
d.ClientOptions = []func(o *s3.Options){getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED)}
359+
})
328360

329361
getBuf := manager.NewWriteAtBuffer([]byte{})
330362

@@ -391,7 +423,7 @@ func (c *S3Client) PutObject(ctx context.Context, key *string, body []byte, stor
391423
input.Metadata = meta.Metadata
392424
}
393425

394-
output, err := c.client.PutObject(ctx, input)
426+
output, err := c.client.PutObject(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
395427
if err != nil {
396428
log.Printf("S3> Got an error uploading file - %s\n", err.Error())
397429
// return nil, err
@@ -413,7 +445,7 @@ func (c *S3Client) DeleteObject(ctx context.Context, key *string) (err error) {
413445
Bucket: &c.bucket,
414446
Key: key,
415447
}
416-
_, err = c.client.DeleteObject(ctx, input)
448+
_, err = c.client.DeleteObject(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
417449
if err != nil {
418450
log.Printf("S3> Failed to delete object %s - %s\n", *key, err.Error())
419451
return err
@@ -444,7 +476,7 @@ func (c *S3Client) CreateMultipartUpload(ctx context.Context, key, storageClass,
444476
input.Metadata = meta.Metadata
445477
}
446478

447-
output, err := c.client.CreateMultipartUpload(ctx, input)
479+
output, err := c.client.CreateMultipartUpload(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
448480
if err != nil {
449481
log.Printf("S3> Unable to create multipart upload for %s - %s\n", *key, err.Error())
450482
} else {
@@ -475,7 +507,7 @@ func (c *S3Client) UploadPart(ctx context.Context, key *string, body []byte, upl
475507
ContentMD5: &contentMD5,
476508
}
477509

478-
output, err := c.client.UploadPart(ctx, input)
510+
output, err := c.client.UploadPart(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
479511
if err != nil {
480512
log.Printf("S3> Failed to upload part for %s - %s\n", *key, err.Error())
481513
// return nil, err
@@ -513,7 +545,7 @@ func (c *S3Client) CompleteMultipartUpload(ctx context.Context, key, uploadID *s
513545
MultipartUpload: &types.CompletedMultipartUpload{Parts: completedPart},
514546
}
515547

516-
output, err := c.client.CompleteMultipartUpload(ctx, input)
548+
output, err := c.client.CompleteMultipartUpload(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
517549
if err != nil {
518550
log.Printf("S3> Unable to complete multipart upload for %s - %s\n", *key, err.Error())
519551
} else {
@@ -540,7 +572,7 @@ func (c *S3Client) ListParts(ctx context.Context, key, uploadID *string) (parts
540572
parts = make(map[int]*Part, 10000)
541573

542574
for {
543-
output, err := c.client.ListParts(ctx, input)
575+
output, err := c.client.ListParts(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
544576
if err != nil {
545577
log.Printf("Failed to list parts for %s - %s\n", *key, err.Error())
546578
break
@@ -574,7 +606,7 @@ func (c *S3Client) GetUploadID(ctx context.Context, key *string) (uploadID *stri
574606
// MaxUploads: 1,
575607
}
576608

577-
output, err := c.client.ListMultipartUploads(ctx, input)
609+
output, err := c.client.ListMultipartUploads(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
578610
if err != nil {
579611
log.Printf("S3> Failed to list multipart uploads - %s\n", err.Error())
580612
return nil
@@ -600,7 +632,7 @@ func (c *S3Client) AbortMultipartUpload(ctx context.Context, key, uploadID *stri
600632
Key: key,
601633
UploadId: uploadID,
602634
}
603-
_, err = c.client.AbortMultipartUpload(ctx, input)
635+
_, err = c.client.AbortMultipartUpload(ctx, input, getClientCredentialsModifyFn(c.isSrcClient, SRC_CRED, DST_CRED))
604636
if err != nil {
605637
log.Printf("S3> Failed to abort multipart upload for %s - %s\n", *key, err.Error())
606638
return err

dth/common.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ var (
3333
MB int = 1 << 20
3434
)
3535

36+
// Global variables which are updated automatically every 02:00 a.m.
37+
// When calling a S3 API, the credentials of the client will be updated according to these two variables
38+
var (
39+
SRC_CRED *S3Credentials
40+
DST_CRED *S3Credentials
41+
)
42+
3643
// Source is an interface represents a type of cloud storage services
3744
// type Source interface {
3845
// GetEndpointURL()

dth/job.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ func NewFinder(ctx context.Context, cfg *JobConfig) (f *Finder) {
117117
srcClient := NewS3Client(ctx, cfg.SrcBucket, cfg.SrcPrefix, cfg.SrcPrefixList, cfg.SrcEndpoint, cfg.SrcRegion, cfg.SrcType, srcCred)
118118
desClient := NewS3Client(ctx, cfg.DestBucket, cfg.DestPrefix, "", "", cfg.DestRegion, "Amazon_S3", desCred)
119119

120+
if srcClient != nil {
121+
srcClient.isSrcClient = true
122+
}
123+
124+
SRC_CRED = srcCred
125+
DST_CRED = desCred
126+
120127
f = &Finder{
121128
srcClient: srcClient,
122129
desClient: desClient,
@@ -126,6 +133,36 @@ func NewFinder(ctx context.Context, cfg *JobConfig) (f *Finder) {
126133
return
127134
}
128135

136+
func RunTicker(ctx context.Context, cfg *JobConfig) {
137+
t := &JobTicker{}
138+
t.updateTimer()
139+
for {
140+
select {
141+
case <-ctx.Done():
142+
log.Printf("timer has been stopped")
143+
t.timer.Stop()
144+
case <-t.timer.C:
145+
log.Printf("ticker update S3 Credentials")
146+
updateCreds(ctx, cfg)
147+
t.updateTimer()
148+
}
149+
}
150+
}
151+
152+
func updateCreds(ctx context.Context, cfg *JobConfig) {
153+
if cfg == nil {
154+
log.Print("nil config")
155+
return
156+
}
157+
sm, err := NewSecretService(ctx)
158+
if err != nil {
159+
log.Printf("Warning - Unable to load credentials, use default setting - %s\n", err.Error())
160+
}
161+
SRC_CRED = getCredentials(ctx, cfg.SrcCredential, cfg.SrcInCurrentAccount, sm)
162+
DST_CRED = getCredentials(ctx, cfg.DestCredential, cfg.DestInCurrentAccount, sm)
163+
log.Print("src_cred and dst_cred has been updated")
164+
}
165+
129166
// Run is main execution function for Finder.
130167
func (f *Finder) Run(ctx context.Context) {
131168

@@ -422,6 +459,13 @@ func NewWorker(ctx context.Context, cfg *JobConfig) (w *Worker) {
422459
srcClient := NewS3Client(ctx, cfg.SrcBucket, cfg.SrcPrefix, cfg.SrcPrefixList, cfg.SrcEndpoint, cfg.SrcRegion, cfg.SrcType, srcCred)
423460
desClient := NewS3Client(ctx, cfg.DestBucket, cfg.DestPrefix, "", "", cfg.DestRegion, "Amazon_S3", desCred)
424461

462+
if srcClient != nil {
463+
srcClient.isSrcClient = true
464+
}
465+
466+
SRC_CRED = srcCred
467+
DST_CRED = desCred
468+
425469
return &Worker{
426470
srcClient: srcClient,
427471
desClient: desClient,
@@ -600,6 +644,13 @@ func (w *Worker) processResult(ctx context.Context, obj *Object, rh *string, res
600644
log.Printf("----->Transferred 1 object %s with status %s\n", obj.Key, res.status)
601645
w.db.UpdateItem(ctx, &obj.Key, res)
602646

647+
if res.status == "ERROR" {
648+
if strings.Contains(res.err.Error(), "403") {
649+
log.Printf("Authentication failed, will update credentials")
650+
updateCreds(ctx, w.cfg)
651+
}
652+
}
653+
603654
if res.status == "DONE" || res.status == "CANCEL" {
604655
w.sqs.DeleteMessage(ctx, rh)
605656
}

dth/ticker.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dth
2+
3+
import (
4+
"log"
5+
"time"
6+
)
7+
8+
const INTERVAL_PERIOD time.Duration = 24 * time.Hour
9+
10+
const HOUR_TO_TICK int = 02
11+
const MINUTE_TO_TICK int = 00
12+
const SECOND_TO_TICK int = 00
13+
14+
type JobTicker struct {
15+
timer *time.Timer
16+
}
17+
18+
func (t *JobTicker) updateTimer() {
19+
nextTick := time.Date(time.Now().Year(), time.Now().Month(),
20+
time.Now().Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local)
21+
if !nextTick.After(time.Now()) {
22+
nextTick = nextTick.Add(INTERVAL_PERIOD)
23+
}
24+
log.Printf("next tick is:%v", nextTick)
25+
diff := nextTick.Sub(time.Now())
26+
if diff < 0 {
27+
diff = INTERVAL_PERIOD
28+
}
29+
if t.timer == nil {
30+
t.timer = time.NewTimer(diff)
31+
} else {
32+
t.timer.Reset(diff)
33+
}
34+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ require (
1515
github.com/aws/smithy-go v1.7.0
1616
github.com/spf13/cobra v1.1.3
1717
github.com/spf13/viper v1.7.1
18+
golang.org/x/sys v0.3.0 // indirect
1819
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
314314
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
315315
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
316316
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
317+
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
318+
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
317319
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
318320
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
319321
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

0 commit comments

Comments
 (0)